Syntax & rules

Cascade layers

Layer ownership, ordering and interactions with native CSS.

How layers control the cascade

Within the same origin and importance, CSS cascade layers order groups of declarations before selector specificity is compared. Master CSS assigns generated rules to five named layers. Load the base stylesheet, normally through @import "@master/css", to establish their order.

The default @master/css/base.css stylesheet declares the layer order:

CSS
@layer theme, base, defaults, components, utilities;

For normal declarations in these layers, later layers have higher priority:

TXT
utilities > components > defaults > base > theme

Normal unlayered author rules take precedence over normal layered author rules. !important reverses the order between layers; it also gives important layered declarations precedence over important unlayered declarations. The order above describes normal declarations, not every case in the native cascade.

Generated CSS emits rules into those layer blocks. Native style rules that use @variant lower to ordinary CSS in the stylesheet that contains them. These rules keep their authored layer, or remain unlayered when no layer was specified. Generated layer blocks do not add the base order statement themselves. See the working card comparison for a normal utility override.

The five layers

Theme

Use theme for generated CSS custom properties. Regular tokens defined in @theme are emitted here when a generated rule needs them. @theme static emits initial resources without a consuming class; @theme inline resolves values into declarations instead of emitting a variable for that token.

Theme rules are intentionally early: they provide values that later layers consume, but they should not compete with layout, color, or component declarations directly.

Base

Use base for low-level resets, normalization, and structural document baselines. This layer should make the browser environment predictable, not express a brand or component design.

Use the @base class suffix for a generated rule, or write native CSS inside @layer base.

Defaults

Use defaults for broad visual choices made by the project, such as the page's text color, typography inside an article, or code styling that should remain below component and utility styles. These are not the browser's built-in default styles.

Use the @default class suffix, native CSS inside @layer defaults. Normal declarations in components and utilities take precedence over normal declarations in this layer.

For example, put button { font: inherit; } in base, body { color: var(--color-text-body); } in defaults, and .btn { display: inline-flex; } in components.

Components

Use components for project styles that carry product meaning: .btn, .card, .field, .toolbar, .app-shell. In project CSS, write native class selectors inside @layer components.

CSS
@layer components {  .btn {    display: inline-flex;    background-color: var(--color-blue-60);    color: oklch(100% 0 none);  }}
HTML
<button type="button" class="btn">Preview button</button>
CSS
@layer components {  .btn {    background-color: var(--color-blue-60);    color: oklch(100% 0 none);    display: inline-flex;  }}@layer theme {  :root,  :host {    --color-blue-60: oklch(51.83% .2687 266.1)  }}

These native rules are emitted by default, even when unused. They follow CSS source order within the layer. Only explicit native pruning can remove unused selectors; .btn does not acquire Master suffixes or become a composition target.

Utilities

Use utilities for built-in utilities, animation utilities, and custom static utilities written in @utilities. Custom utilities can use native declarations, nested selectors, and @variant blocks.

index.css
@utilities {  flow {    display: grid;    gap: var(--spacing-md);  }  content-auto {    content-visibility: auto;    contain-intrinsic-size: auto 32rem;  }}

Utilities is the last layer in the base order. Its normal declarations can override normal component declarations when both rules match the element.

A complete flow

Add this configuration to the project CSS entry that imports @master/css. It defines mode-aware variables and a component; the markup below also generates base, defaults and utility rules.

CSS
@mode light {  .light {    @slot;  }}@mode dark {  .dark {    @slot;  }}@theme light {  --color-primary: #000000;}@theme dark {  --color-primary: #ffffff;}@layer components {  .btn {    display: inline-flex;    background-color: var(--color-primary);  }}
HTML
<body class="list-style:none_ul@base">  <button class="btn flex">Submit</button>  <ul class="animation:fade|1s">…</ul>  <article class="text:1rem_p@default">    <p>…</p>  </article></body>
CSS
@layer components {  .btn {    background-color: var(--color-primary);    display: inline-flex;  }}@layer theme {  .light {    --color-primary: #000  }  .dark {    --color-primary: #fff  }}@layer base {  .list-style\:none_ul\@base ul {    list-style: none  }}@layer defaults {  .text\:1rem_p\@default p {    font-size: 1rem;    line-height: max(1.8em - max(0rem, 1rem - 1rem) * 1.12, 1rem);    letter-spacing: clamp(-.072em, calc((1rem - 1rem) * -.048), 0em)  }}@layer utilities {  .flex {    display: flex  }  .animation\:fade\|1s {    animation: fade 1s  }}@keyframes fade {  0% {    opacity: 0  }  to {    opacity: 1  }}

Notice the shape of the output:

  • @master/css/base.css defines the global layer order once.

  • Theme variables are emitted in @layer theme.

  • The .btn definition is emitted in @layer components.

  • flex and animation:fade|1s are emitted in @layer utilities.

  • @keyframes are emitted at the top level, outside cascade layers.

Base is for normalization

Put reset-style rules in the base layer when they should sit below normal defaults, components and utility declarations.

Add base rules in the stylesheet your app loads when the reset is part of the application shell.

globals.css
@layer base {  ul {    list-style: none;  }}

You can also use @base from markup:

HTML
<body class="list-style:none_ul@base">…</body>
CSS
@layer base {  .list-style\:none_ul\@base ul {    list-style: none  }}

In most projects, import the default @master/css stylesheet; it includes base styles in the base layer.

Defaults are for broad defaults

Put brand or content defaults in the defaults layer when they should apply across selected descendants but still remain easy to override.

HTML
<body class="font-mono_:is(code,pre)@default">…</body>
CSS
@layer theme {  :root,  :host {    --font-family-mono: var(--font-mono, ui-monospace), SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace  }}@layer defaults {  .font-mono_\:is\(code\,pre\)\@default :is(code, pre) {    font-family: var(--font-family-mono)  }}

Avoid using the base layer for brand or content typography:

HTML
<body class="font-mono_:is(code,pre)@base">…</body>

Base should normalize. Defaults should express broad design defaults.

Utilities can override components

With the base order loaded, normal utility declarations take precedence over normal component declarations, independently of selector specificity between those layers.

Here, btn defines display: inline-flex. The same button also uses flex, so the utilities layer sets its final display to flex:

CSS
@layer components {  .btn {    display: inline-flex;    background-color: var(--color-blue-60);    color: oklch(100% 0 none);  }}
HTML
<button type="button" class="btn flex">Preview button</button>
CSS
@layer components {  .btn {    background-color: var(--color-blue-60);    color: oklch(100% 0 none);    display: inline-flex;  }}@layer theme {  :root,  :host {    --color-blue-60: oklch(51.83% .2687 266.1)  }}@layer utilities {  .flex {    display: flex  }}

Use this as the normal override path: keep reusable product styles in components, then use utilities in utilities for local adjustments.

Defaults stay below local decisions

Defaults rules are useful for descendants because they can establish defaults without blocking local classes.

For example, set paragraph text to 1rem across an article, then override one paragraph with text:1.5rem.

HTML
<article class="text:1rem_p@default">    <p class="text:1.5rem">24</p>  <p>16</p></article>
CSS
@layer defaults {  .text\:1rem_p\@default p {    font-size: 1rem;    line-height: max(1.8em - max(0rem, 1rem - 1rem) * 1.12, 1rem);    letter-spacing: clamp(-.072em, calc((1rem - 1rem) * -.048), 0em)  }}@layer utilities {  .text\:1\.5rem {    font-size: 1.5rem;    line-height: max(1.8em - max(0rem, 1.5rem - 1rem) * 1.12, 1.5rem);    letter-spacing: clamp(-.072em, calc((1.5rem - 1rem) * -.048), 0em)  }}

This is the reason defaults sit before components and utilities: defaults should be broad, but final element-level decisions should stay close to the element.

Writing regular CSS

Regular CSS outside @layer follows the native cascade outside Master CSS's layer model. If you want a rule to participate in the same priority system, place it in the layer that matches its responsibility.

app.css
@layer defaults {  article :is(h1, h2, h3) {    font-weight: 700;  }}@layer components {  .card {    border-radius: .75rem;  }}

Native @layer blocks remain ordinary stylesheet output; they do not define on-demand classes. For on-demand managed definitions, use the explicit directive forms:

index.css
@utilities {  content-auto {    content-visibility: auto;  }}@layer components {  .card {    display: block;    border-radius: .75rem;  }}

Write native declarations in the layer that owns the rule:

app.css
.card {  display: block;  padding: var(--spacing-md);  @dark {    background-color: var(--color-neutral-90);  }}

Priority within and between layers

Class order in HTML does not choose the winner. Master CSS generates a stable rule order; the browser then applies its native cascade to matching declarations. The following cases assume the base layer statement is loaded and the root font size is 16px:

Competing classes or rulesResultReason
p-md p:8px, in either orderpadding: 8pxA direct value overrides a token for the same property and scope.
p-md! p:8pxpadding: 1remImportance takes precedence over value source.
p-md! p:8px!padding: 8pxWith equal importance, the direct value wins.
p:8px@base p:12pxpadding: 12pxNormal utilities take precedence over the base layer.
p:8px@base! p:12px!padding: 8pxImportant declarations reverse layer precedence.
Unlayered .outside { padding: 32px; } and p:8pxpadding: 32pxNormal unlayered CSS takes precedence over layered CSS.
Component-layer padding: 24px !important and p:8px!padding: 24pxThe earlier component layer wins for important declarations.
p:8px pt:12pxTop padding is 12px; other sides are 8pxThe longhand supplies the more specific property override.

Selector and condition scope still matter. A hover or breakpoint rule only participates while its selector and conditions match. Direct values do not universally override tokens in other layers or scopes.

Groups retain each member's property and value source. Native declarations retain duplicates and source order, including fallback values. A shorthand may reset properties omitted from its value, just as it does in CSS. @compose is removed; use native declarations and selectors in stylesheets and utilities directly in markup. See the directive migration notice.

Canonical spelling changes must preserve cascade behavior as well as declaration values. A tool cannot safely rewrite a class merely because its isolated CSS looks equivalent. Internal ordering keys and final deterministic tie-breakers are not extension APIs.

Layer checklist

Use the five layers in their declared order:

  1. theme provides generated variables.

  2. base normalizes the platform.

  3. defaults sets broad defaults.

  4. components holds project vocabulary from CSS-defined component classes.

  5. utilities holds utilities and final local adjustments.

When a declaration loses, first confirm that its selector and conditions match. Then inspect importance, origin, layer and specificity in the browser. Include native unlayered rules in that check; layer order alone does not explain every result.


© 2026 Aoyue Design LLC.MIT License
Trademark Policy