Class names & the cascade

Deterministic class names, rule ordering, specificity, conflicts and the preflight reset.

1 min readv1

Deterministic names#

A class name is <prefix><hash> of the normalised rule — nib- plus an FNV-1a hash in base 36. The same rule always gives the same class, so:

  • server and client HTML match (no hydration warnings),
  • output is cacheable and diffs are stable,
  • identical rules are emitted once, however many elements use them.

Change the prefix with prefix: "x-" in the config.

Specificity#

Every generated selector has one class of specificity — or one class plus .dark for dark rules, plus a pseudo for state rules. That is intentionally low, so:

  • your own stylesheets can override NibCSS with a single class,
  • NibCSS rules on the same element never need !important to beat each other.

Order#

  1. @layer nib-preflight — the reset
  2. base rules, in first-seen order
  3. @media blocks, in first-seen order

So a responsive rule always overrides its base rule.

Conflicts on one element#

Two rules for the same property on one element (p=8px; p=16px) produce two classes with equal specificity; the one emitted later in the stylesheet wins. Avoid relying on that — write the property once, or override a recipe property after the recipe, which is the documented pattern.

The preflight reset#

The engine emits a small base reset — box-sizing: border-box, zeroed margins and padding, sensible media and form defaults — inside @layer nib-preflight. A cascade layer loses to unlayered CSS, so your stylesheet always wins over it, and utilities always win over both.

TypeScript
export default defineConfig({
  preflight: false,                         // no reset
  // preflight: "*, *::before { box-sizing: border-box }",   // your own
});

Next steps#