Cascade layers

The three layers this package owns, and why declaring the full order is the consuming project's job.

css-base scopes every rule it ships to one of three cascade layers it owns.

LayerContents
@layer resetThe CSS reset, the foundational baseline
@layer baseNative element styles
@layer layoutsLayout primitives

Custom media queries in css/mediaqueries.css are the exception: @custom-media is resolved at build time, so it is never subject to the cascade and sits outside any layer.

Declare the order in your project

The full layer order, including layers owned by sibling packages (tokens, components) and by the project itself (libs, vendors, pages, utilities), belongs to the consuming project. css-base deliberately does not declare it.

The reason is a rule of the cascade: a layer’s position is fixed the first time its name is seen, and later re-declarations do not reorder it. If an imported package declared the order, it would win, and your project could never change it.

So declare the order once, at the very top of your entry stylesheet, before any @import:

/* your project entry, e.g. main.css */
@layer reset, tokens, libs, base, vendors, layouts, components, pages, utilities;

@import '@uncinq/design-tokens';    /* @layer tokens */
@import '@uncinq/css-base';         /* @layer reset, base, layouts */
@import '@uncinq/component-tokens'; /* @layer tokens */
@import '@uncinq/css-components';   /* @layer components */

The last layer wins. reset and tokens come first, at the lowest priority, so reset rules and token defaults never accidentally override base, layout, vendor or component styles.

If the @layer ...; line comes after an import, it is already too late: the imported package has fixed the order. Keep it first, always.

Why libs and vendors are a pair

A third-party stylesheet goes into libs; the CSS you write to dress it goes into vendors, right after base. libs sits low, just above tokens, so a library never wins over your native element styles; vendors is override code, and dressing a library’s markup often means undoing a base rule on its button, input or ul. Keeping them separate matters whenever the library is loaded lazily: it then arrives after your own CSS, and inside a single shared layer it would win every tie on source order.

Declare libs even when nothing imports into it yet. An undeclared layer name is appended last, above utilities, and CSS outside any layer is stronger still. A library imported with no layer() at all would therefore beat everything you write.

Overriding from your project

Write to the same layer name after the imports.

Most of the time there is a token for what you want to change, and it is read from :root, so redefine it there:

@layer base {
  :root {
    --color-link: #0070f3;
  }
}

Rewrite a rule only when no token covers the declaration:

@layer base {
  table {
    width: auto;
  }
}

Because css-base uses low-specificity selectors throughout, and :where() in several files, such an override rarely needs to escalate specificity.

Setting a token on a selector rather than on :root is for a scoped change, where only one subtree should follow the new value:

@layer base {
  footer {
    --color-link: currentColor;
  }
}

Reference

MDN: Using CSS cascade layers