zfb
GitHub repository

Type to search...

to open search from anywhere

Cascade and reset

Understand reset choices, generated layers, unlayered utilities, and deterministic rule ordering.

Choose a reset for the page

The documentation chrome still uses Tailwind CSS. These isolated samples contain compiled zudo-wind CSS and browser-native styles only.

ResetChoose it whenBrowser defaults that remain
noneYour app already owns a baselineHeading sizes, list markers, body margin, native controls
minimal-v1You need box sizing, a flush body, and inherited control fontsHeading sizes, list markers, other UA styles
owned-v1You want the compact reset zudo-wind ownsFocus outlines and native appearance

The same HTML is compiled three times. No scaffold CSS is added, so heading, list, margin, and control differences remain visible. In your project, compile the configured entry and audit the build plan with:

zfb css --input src/styles.css --output dist/app.css --project-root .
zfb wind audit --plan build
Reset none: browser defaults
HTML
<main id="task-list"><h2>Task list</h2><ul><li>Review source roots</li><li>Check the manifest</li></ul><form><label><input type="checkbox"> Notify the team</label><button type="button">Save</button></form><p><a href="#task-list" class="underline">Open task details</a></p></main>
CSS
@layer zw-reset, zw-tokens, zfb-hi, base, components;
@layer zw-tokens {
  :root {
    --zw-spacing-unit: .25rem;
  }
}

.underline {
  text-decoration-line: underline;
}
Head
<base href="about:srcdoc">
Configuration for this example
{
  "wind": {
    "spec": 1,
    "reset": "none",
    "strict": true,
    "tokens": {
      "spacingUnit": "0.25rem"
    },
    "breakpoints": {},
    "defaultTransitionTimingFunction": "ease",
    "authoredClasses": {},
    "safelist": {
      "docs-preview": [
        "underline"
      ]
    }
  }
}
Authored demonstration CSS
Minimal reset: owned basics
HTML
<main id="task-list"><h2>Task list</h2><ul><li>Review source roots</li><li>Check the manifest</li></ul><form><label><input type="checkbox"> Notify the team</label><button type="button">Save</button></form><p><a href="#task-list" class="underline">Open task details</a></p></main>
CSS
@layer zw-reset, zw-tokens, zfb-hi, base, components;
@layer zw-reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }
  body {
    margin: 0;
  }
  button,
  input,
  optgroup,
  select,
  textarea {
    font: inherit;
  }
}
@layer zw-tokens {
  :root {
    --zw-spacing-unit: .25rem;
  }
}

.underline {
  text-decoration-line: underline;
}
Head
<base href="about:srcdoc">
Configuration for this example
{
  "wind": {
    "spec": 1,
    "reset": "minimal-v1",
    "strict": true,
    "tokens": {
      "spacingUnit": "0.25rem"
    },
    "breakpoints": {},
    "defaultTransitionTimingFunction": "ease",
    "authoredClasses": {},
    "safelist": {
      "docs-preview": [
        "underline"
      ]
    }
  }
}
Authored demonstration CSS
Owned reset: normalized headings and lists
HTML
<main id="task-list"><h2>Task list</h2><ul><li>Review source roots</li><li>Check the manifest</li></ul><form><label><input type="checkbox"> Notify the team</label><button type="button">Save</button></form><p><a href="#task-list" class="underline">Open task details</a></p></main>
CSS
@layer zw-reset, zw-tokens, zfb-hi, base, components;
@layer zw-reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border-width: 0;
    border-style: solid;
    border-color: currentColor;
  }
  html {
    line-height: 1.5;
    font-family: ui-sans-serif, system-ui, sans-serif;
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
  }
  body {
    line-height: inherit;
  }
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    font-size: inherit;
    font-weight: inherit;
  }
  a {
    color: inherit;
    text-decoration: inherit;
  }
  b,
  strong {
    font-weight: bolder;
  }
  code,
  kbd,
  samp,
  pre {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 1em;
  }
  ol,
  ul,
  menu {
    list-style: none;
  }
  button,
  input,
  optgroup,
  select,
  textarea {
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
  }
  button,
  input:where([type="button"], [type="reset"], [type="submit"]) {
    background-color: transparent;
    background-image: none;
  }
  textarea {
    resize: vertical;
  }
  img,
  svg,
  video,
  canvas,
  audio,
  iframe,
  embed,
  object {
    display: block;
    vertical-align: middle;
  }
  img,
  video {
    max-width: 100%;
    height: auto;
  }
  table {
    border-collapse: collapse;
    border-color: inherit;
    text-indent: 0;
  }
  summary {
    display: list-item;
  }
  hr {
    height: 0;
    border-top-width: 1px;
  }
}
@layer zw-tokens {
  :root {
    --zw-spacing-unit: .25rem;
  }
}

.underline {
  text-decoration-line: underline;
}
Head
<base href="about:srcdoc">
Configuration for this example
{
  "wind": {
    "spec": 1,
    "reset": "owned-v1",
    "strict": true,
    "tokens": {
      "spacingUnit": "0.25rem"
    },
    "breakpoints": {},
    "defaultTransitionTimingFunction": "ease",
    "authoredClasses": {},
    "safelist": {
      "docs-preview": [
        "underline"
      ]
    }
  }
}
Authored demonstration CSS

Resolve a cascade tie

Utilities are unlayered and beat normal declarations in named layers. For an unlayered authored selector and utility with equal specificity, placement decides which rule comes later:

wind: {
  utilities: { placement: "before-authored" }
}

The records compare an actual p-4 rule with an authored padding declaration. The default puts the utility after authored global CSS; before-authored puts the authored rule last:

Unlayered utility beats a normal layered rule
HTML
<div class="wind-demo-stage"><div class="wind-demo-box p-4"><p>Padding from an unlayered utility</p></div></div>
CSS
@layer zw-reset, zw-tokens, zfb-hi, base, components;
@layer zw-tokens {
  :root {
    --zw-spacing-unit: .25rem;
  }
}
.wind-demo-stage {
  max-width: 24rem;
  padding: 1rem;
  background: #f1f5f9;
}

@layer components {
  .wind-demo-box {
    padding: .5rem;
    border: 1px solid #94a3b8;
    background: #fff;
  }
}

.wind-demo-box p {
  margin: 0;
}
.p-4 {
  padding-top: 1rem;
  padding-right: 1rem;
  padding-bottom: 1rem;
  padding-left: 1rem;
}
Configuration for this example
{
  "wind": {
    "spec": 1,
    "reset": "none",
    "strict": true,
    "tokens": {
      "spacingUnit": "0.25rem"
    },
    "breakpoints": {},
    "defaultTransitionTimingFunction": "ease",
    "utilities": {
      "placement": "after-authored"
    },
    "authoredClasses": {
      "wind-demo-box": true,
      "wind-demo-stage": true
    },
    "safelist": {
      "docs-preview": [
        "p-4"
      ]
    }
  }
}
Authored demonstration CSS
.wind-demo-stage { max-width: 24rem; padding: 1rem; background: #f1f5f9; }
@layer components { .wind-demo-box { padding: 0.5rem; border: 1px solid #94a3b8; background: #ffffff; } }
.wind-demo-box p { margin: 0; }
Default placement: utility wins an equal-specificity tie
HTML
<div class="wind-demo-stage"><div class="wind-demo-box p-4"><p>Padding from the later rule</p></div></div>
CSS
@layer zw-reset, zw-tokens, zfb-hi, base, components;
@layer zw-tokens {
  :root {
    --zw-spacing-unit: .25rem;
  }
}
.wind-demo-stage {
  max-width: 24rem;
  padding: 1rem;
  background: #f1f5f9;
}

.wind-demo-box {
  padding: .5rem;
  border: 1px solid #94a3b8;
  background: #fff;
}

.wind-demo-box p {
  margin: 0;
}
.p-4 {
  padding-top: 1rem;
  padding-right: 1rem;
  padding-bottom: 1rem;
  padding-left: 1rem;
}
Configuration for this example
{
  "wind": {
    "spec": 1,
    "reset": "none",
    "strict": true,
    "tokens": {
      "spacingUnit": "0.25rem"
    },
    "breakpoints": {},
    "defaultTransitionTimingFunction": "ease",
    "utilities": {
      "placement": "after-authored"
    },
    "authoredClasses": {
      "wind-demo-box": true,
      "wind-demo-stage": true
    },
    "safelist": {
      "docs-preview": [
        "p-4"
      ]
    }
  }
}
Authored demonstration CSS
.wind-demo-stage { max-width: 24rem; padding: 1rem; background: #f1f5f9; }
.wind-demo-box { padding: 0.5rem; border: 1px solid #94a3b8; background: #ffffff; }
.wind-demo-box p { margin: 0; }
Before-authored placement: authored rule wins the tie
HTML
<div class="wind-demo-stage"><div class="wind-demo-box p-4"><p>Padding from the later rule</p></div></div>
CSS
@layer zw-reset, zw-tokens, zfb-hi, base, components;
@layer zw-tokens {
  :root {
    --zw-spacing-unit: .25rem;
  }
}
.p-4 {
  padding-top: 1rem;
  padding-right: 1rem;
  padding-bottom: 1rem;
  padding-left: 1rem;
}
.wind-demo-stage {
  max-width: 24rem;
  padding: 1rem;
  background: #f1f5f9;
}

.wind-demo-box {
  padding: .5rem;
  border: 1px solid #94a3b8;
  background: #fff;
}

.wind-demo-box p {
  margin: 0;
}
Configuration for this example
{
  "wind": {
    "spec": 1,
    "reset": "none",
    "strict": true,
    "tokens": {
      "spacingUnit": "0.25rem"
    },
    "breakpoints": {},
    "defaultTransitionTimingFunction": "ease",
    "utilities": {
      "placement": "before-authored"
    },
    "authoredClasses": {
      "wind-demo-box": true,
      "wind-demo-stage": true
    },
    "safelist": {
      "docs-preview": [
        "p-4"
      ]
    }
  }
}
Authored demonstration CSS
.wind-demo-stage { max-width: 24rem; padding: 1rem; background: #f1f5f9; }
.wind-demo-box { padding: 0.5rem; border: 1px solid #94a3b8; background: #ffffff; }
.wind-demo-box p { margin: 0; }

Placement changes source order, not selector specificity. group-* and peer-* keep their zudo-wind specificity; see relation variant specificity.

Technical reference

Layer order

Whenever wind emits a reset, token, or utility rule, the generated layer prelude is:

@layer zw-reset, zw-tokens, zfb-hi, base, components;

Wind appends authored layer-order statements after this prelude. They cannot move the five listed layers ahead of or behind one another. Other layer names follow ordinary CSS first-declaration order. Imported stylesheets are not wrapped in a layer implicitly; an explicit import layer remains valid.

The integrated stylesheet order is:

  1. The generated layer prelude.

  2. Hoisted external imports.

  3. The highlight block in @layer zfb-hi.

  4. Generated reset and token layers.

  5. Generated @property registrations when needed.

  6. Authored bundled CSS, with imports already handled by the bundler.

  7. Unlayered generated utility rules.

  8. CSS Modules output at its pipeline append position.

Items 6 and 7 trade places under wind.utilities.placement: "before-authored"; see Utility placement.

The prelude comes before any authored layer prefix, so authored CSS cannot replace the owned order. Wind utilities are unlayered and, by default, follow authored global rules. They therefore beat normal declarations in named layers. An unlayered authored rule with greater specificity still wins. Between an unlayered authored rule and a utility of equal specificity, the one that comes later wins: the utility by default, the authored rule under before-authored placement. Important declarations follow normal CSS layer rules, but there is no important modifier in the wind language.

An entirely empty wind result emits no prelude. A selected reset can emit the prelude and reset even when no candidates resolve.

Utility placement

wind.utilities.placement chooses where the unlayered utility rules sit relative to authored global CSS:

wind: {
  utilities: { placement: "before-authored" }
}
ValueOrder of items 6 and 7Equal-specificity tie
"after-authored" (default)Authored global CSS, then utilitiesThe utility wins
"before-authored"Utilities, then authored global CSSThe authored rule wins

Nothing else moves. The prelude, external imports, highlight block, reset, tokens, @property registrations, and CSS Modules output keep their places, and no utility is wrapped in a layer. External @import rules are hoisted above both stages either way. The setting applies to zfb build, zfb dev, and zfb css alike; editing it in dev rebuilds the served stylesheet.

Placement only settles ties of equal specificity. It does not change any selector, so it does not restore the specificity of Tailwind's group-* and peer-* selectors; see Relation variants.

Reset choices

For measured differences from Tailwind 4.2.0 preflight and an authored parity block, see Differences from Tailwind preflight.

The default is none, which emits no reset bytes. minimal-v1 contains exactly these declarations:

*,
*::before,
*::after {
  box-sizing: border-box;
}
body {
  margin: 0;
}
button,
input,
optgroup,
select,
textarea {
  font: inherit;
}

owned-v1 is a complete reset, not an extension of minimal-v1:

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  border-width: 0;
  border-style: solid;
  border-color: currentColor;
}
html {
  line-height: 1.5;
  font-family: ui-sans-serif, system-ui, sans-serif;
  -webkit-text-size-adjust: 100%;
  tab-size: 4;
}
body {
  line-height: inherit;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: inherit;
  font-weight: inherit;
}
a {
  color: inherit;
  text-decoration: inherit;
}
b,
strong {
  font-weight: bolder;
}
code,
kbd,
samp,
pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1em;
}
ol,
ul,
menu {
  list-style: none;
}
button,
input,
optgroup,
select,
textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}
button,
input:where([type="button"], [type="reset"], [type="submit"]) {
  background-color: transparent;
  background-image: none;
}
textarea {
  resize: vertical;
}
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
  display: block;
  vertical-align: middle;
}
img,
video {
  max-width: 100%;
  height: auto;
}
table {
  border-collapse: collapse;
  border-color: inherit;
  text-indent: 0;
}
summary {
  display: list-item;
}
hr {
  height: 0;
  border-top-width: 1px;
}

Both resets leave native focus outlines and appearance alone. Neither forces a display rule for [hidden]. Bare border utilities set a solid border themselves under every reset choice.

Deterministic ordering

Rules sort by this ascending tuple:

(responsive_rank, dark_rank, pointer_rank, relation_rank, state_rank, pseudo_rank,
 conflict_group_rank, scope_rank, catalog_rank, raw_candidate_utf8_bytes)

The base responsive rank is 0. For each configured breakpoint in ascending width order, minimum ranks are 2*i + 1 and maximum ranks are 2*i + 2, with i starting at zero. Dark has rank 0 or 1. Pointer rank is 0 absent, 1 coarse, or 2 fine. Relation, state, and pseudo ranks follow the orders in Variants. Conflict groups use catalog table order starting at 1. Scope ranks are broad 0, axis 1, and side or corner 2; single-property families use 0. Catalog subentries use their listed order, then root bytes; raw candidate UTF-8 bytes break the final tie. This is emitted source order; it does not replace selector specificity.

The public Rust SortKey carries pointer_rank; explain JSON exposes sortTuple.pointerRank, and the text sort line includes pointer=.

HTML class order has no effect. With spacingUnit set to 0.25rem, every permutation of p-4 px-2 pl-1 resolves to top and bottom 1rem, right 0.5rem, and left 0.25rem. The same rule applies to margins. gap-4 gap-x-2 gap-y-1 produces a column gap of 0.5rem and row gap of 0.25rem. Same-scope ties use raw candidate bytes, so p-2 sorts after p-10.

Output bytes

V1 generated CSS is not minified in development or production. There is no final stylesheet minifier. The printer uses LF, two-space indentation, one declaration per line, and a final LF. Identical inputs produce identical wind CSS in either mode; CSS Modules may still be minified separately in production.

See also

Revision History

CreatedUpdated