How it works
The mental model behind NibCSS: from an attribute to rules, classes and a stylesheet — and why the output is deterministic.
You can use NibCSS without knowing its internals, but a correct mental model makes every other page obvious. It fits in five steps.
- 1 · PARSESplit the attribute on
;into rules. - 2 · RESOLVEExpand recipes, replace
$tokens, map shorthands. - 3 · HASHName each rule
nib-<hash>. - 4 · EMITWrite the CSS once — to a file or a
<style>. - 5 · APPLYPut the classes on the element.
1. Parse#
An attribute is a list of rules separated by ;. Each rule is [modifiers:]name[=value][!important]:
nib="p=16px; md:hover:bgc=#111; btn-primary"
───┬─── ─────┬───── ──┬── ─┬── ──────┬────
rule modifiers name value component (no "=")2. Resolve#
- A name without
=is a component recipe from your config and is expanded into its own rules. $tokensin values are replaced:bgc=$color.brand→bgc=#4F52D9.- The name is looked up in the ~290 built-in shorthands (
bgc→background-color), your ownshortcuts, or accepted as a real CSS property (caret-color,--my-var). Anything else is reported and dropped.
3. Hash#
The normalised rule is hashed into a class name. The same rule always produces the same name:
p=16px → .nib-jbmcpl { padding: 16px }
mx=auto → .nib-1yxik1w { margin-left: auto; margin-right: auto }
dark:cl=#fff → .dark .nib-jalsyg { color: #fff }4. Emit#
Each distinct rule is written once, however many elements use it. Base rules come first and media-query blocks after them, so a responsive rule always overrides its mobile default.
5. Apply#
The element receives its class names — at build time (rewrite: true) or by the runtime in the browser.
Build time or run time?#
The same five steps run in two places:
Build time ( | Run time ( | |
|---|---|---|
Static | ✅ extracted into a | ✅ scanned and applied |
Dynamic values ( | — | ✅ generated on the fly |
JavaScript needed in the browser | none (with | ~23 kB |
First paint | styled (render-blocking | styled (see No flash) |
They compose: static markup costs nothing at runtime, and only truly dynamic values use the runtime.
Why deterministic class names matter#
Because a class name is a pure function of its rule:
- Server and client agree. SSR output hydrates without mismatches.
- Caching works. The stylesheet only changes when a new rule appears.
- Diffs are stable. Reordering components does not rename classes.
- No duplication. 500 elements with
p=16pxshare one rule.
One class per rule, one class per recipe#
A utility produces one class. A component recipe produces one class holding all of its declarations — even across modifiers:
/* nib="card-hover" where card-hover = "card; hover:tf=translateY(-4px)" */
.nib-1nfp073 { padding: 24px; border-radius: 12px }
.dark .nib-1nfp073 { background-color: #111 }
.nib-1nfp073:hover { transform: translateY(-4px) }