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:
@layer theme, base, defaults, components, utilities;For normal declarations in these layers, later layers have higher priority:
utilities > components > defaults > base > themeNormal 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.
@layer components { .btn { display: inline-flex; background-color: var(--color-blue-60); color: oklch(100% 0 none); }}<button type="button" class="btn">Preview button</button>@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.
@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.
@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); }}<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>@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.cssdefines the global layer order once.Theme variables are emitted in
@layer theme.The
.btndefinition is emitted in@layer components.flexandanimation:fade|1sare emitted in@layer utilities.@keyframesare 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.
@layer base { ul { list-style: none; }}You can also use @base from markup:
<body class="list-style:none_ul@base">…</body>@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.
<body class="font-mono_:is(code,pre)@default">…</body>@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:
<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:
@layer components { .btn { display: inline-flex; background-color: var(--color-blue-60); color: oklch(100% 0 none); }}<button type="button" class="btn flex">Preview button</button>@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.
<article class="text:1rem_p@default"> <p class="text:1.5rem">24</p> <p>16</p></article>@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.
@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:
@utilities { content-auto { content-visibility: auto; }}@layer components { .card { display: block; border-radius: .75rem; }}Write native declarations in the layer that owns the rule:
.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 rules | Result | Reason |
|---|---|---|
p-md p:8px, in either order | padding: 8px | A direct value overrides a token for the same property and scope. |
p-md! p:8px | padding: 1rem | Importance takes precedence over value source. |
p-md! p:8px! | padding: 8px | With equal importance, the direct value wins. |
p:8px@base p:12px | padding: 12px | Normal utilities take precedence over the base layer. |
p:8px@base! p:12px! | padding: 8px | Important declarations reverse layer precedence. |
Unlayered .outside { padding: 32px; } and p:8px | padding: 32px | Normal unlayered CSS takes precedence over layered CSS. |
Component-layer padding: 24px !important and p:8px! | padding: 24px | The earlier component layer wins for important declarations. |
p:8px pt:12px | Top padding is 12px; other sides are 8px | The 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:
themeprovides generated variables.basenormalizes the platform.defaultssets broad defaults.componentsholds project vocabulary from CSS-defined component classes.utilitiesholds 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.