基礎原理

層疊層

Understand how theme, base, defaults, components, and utilities layers control style priority.

How layers control the cascade

CSS cascade layers let a stylesheet decide which groups of rules should win before selector specificity is compared. Master CSS uses that native model as the backbone of its output, so utilities, project styles, presets, variables, and resets can coexist without turning every override into a specificity contest.

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

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

Layers declared later have higher priority for normal declarations. In Master CSS, the practical order is:

utilities > components > defaults > base > theme
LayerDescriptionCSS
BaseWhere the styles with @base are generated.@layer base { … }
ThemeWhere the used theme tokens are generated.@layer theme { … }
DefaultsWhere the styles with @default are generated.@layer defaults { … }
ComponentsWhere the used component classes are generated.@layer components { … }
UtilitiesWhere the utility styles are generated.@layer utilities { … }

Generated CSS emits rules into those layer blocks. Native style rules that use @compose or @variant lower to ordinary CSS in the stylesheet that contains them. This order is intentional: foundation rules go early, product vocabulary goes in the middle, and one-off utility decisions stay late enough to override project styles when markup asks for it.


The five layers

Base

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

Rules enter the base layer through @base syntax or regular CSS written inside @layer base.

Theme

Use theme for generated CSS custom properties. Tokens defined in @theme are emitted here only when a generated rule references them.

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

Defaults

Use defaults for broad element defaults such as typography inside an article, code styling, or vendor defaults you want to keep below app-level styles.

Rules enter the defaults layer through @default syntax or @defaults managed definitions. Because defaults sit before components and utilities, components and utilities can still override it without !important.

Components

Use components for project styles that carry product meaning: .btn, .card, .field, .toolbar, .app-shell. In project CSS, write these managed class definitions in @components.

@components {    btn {        @compose inline-flex;        background-color: var(--color-primary);    }}

The compiler converts these definitions into static manifest utilities with layer: 'components'. They remain on-demand: defining .btn does not emit CSS until btn is used or scanned from source.

Utilities

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

@utilities {    flow {        @compose grid gap:md;    }    content-auto {        content-visibility: auto;        contain-intrinsic-size: auto 32rem;    }}

Utilities is the final layer, so utility classes can intentionally override component styles from markup.


A complete flow

This example touches every generated layer. The project CSS entry defines theme variables and a component, while the markup uses base, defaults, and utilities.

@theme light {    --color-primary: #000000;}@theme dark {    --color-primary: #ffffff;}@components {    btn {        @compose 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>
Generated CSS
@layer base {    .list-style\:none_ul\@base ul {        list-style: none    }}@layer theme {    .light,    :root {        --color-primary: rgb(0 0 0)    }    .dark {        --color-primary: rgb(255 255 255)    }}@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(-0.072em, calc((1rem - 1rem) * -0.048), 0em)    }}@layer components {    .btn {        display: inline-flex    }    .btn {        background-color: var(--color-primary)    }}@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 every design decision.

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>
Generated 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.

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

Do not put brand defaults into the base layer.

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

Base should normalize. Defaults should express broad design defaults.


Utilities can override components

Layer order is checked before selector specificity. That means a utility can override a component style even when the component style comes from a named component.

For example, .btn defines display: inline-flex in the components layer. Adding flex in markup moves the final display decision to the utilities layer.

<button class="btn flex">Submit</button>
Generated CSS
@layer components {    .btn {        display: inline-flex;    }}@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>
Generated 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(-0.072em, calc((1rem - 1rem) * -0.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(-0.072em, calc((1.5rem - 1rem) * -0.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 is never compiler-managed. For on-demand managed definitions, use the explicit directive forms:

@utilities {    content-auto {        content-visibility: auto;    }}@components {    card {        @compose block;        border-radius: .75rem;    }}

Native style rules can still compose Master CSS utilities when the rule should remain ordinary stylesheet output:

.card {    @compose block p:md;    @dark {        @compose bg:neutral-90;    }}

Summary

Cascade layers are the contract that makes Master CSS predictable:

  1. base normalizes the platform.
  2. theme provides generated variables.
  3. defaults sets broad defaults.
  4. components holds project vocabulary from CSS-defined component classes.
  5. utilities holds utilities and final local adjustments.

When declarations conflict, solve the problem by choosing the correct layer first. Reach for specificity or !important only when you are intentionally integrating with CSS that sits outside this layer model.


  • Master UI


© 2026 Aoyue Design LLC.MIT License
Trademark Policy