No flash of unstyled content
How NibCSS guarantees a styled first paint in every setup — and the two habits that break it.
A stylesheet produced by JavaScript has one hard constraint: anything applied after the browser's first paint is a visible flash. NibCSS avoids it with a different mechanism in each setup.
With a bundler#
Static attributes are extracted into a real .css file, loaded as a render-blocking <link> —
exactly like a hand-written stylesheet. There is nothing left to flash. With rewrite: true the
elements do not even need the runtime.
From a CDN#
Put the script in <head>, with no defer, async or type="module". It starts observing
before <body> is parsed and applies classes at the microtask checkpoint — which runs before the
browser's "update the rendering" step. Elements are styled in the same turn the parser creates them.
Don't | Why |
|---|---|
| runs after parsing — the page has already painted |
| may run after paint |
| modules are deferred by default |
the tag at the end of | everything above it has already painted |
In a client-rendered app#
Elements do not exist until the framework renders them. The adapters resolve classes synchronously during render, so a node carries its class the first time it is committed.
With SSR / SSG#
Render the collected dynamic CSS into the HTML, and the client runtime adopts that same <style id="nib">
element — no duplicate, no gap:
const rt = new NibRuntime({ watch: false, ...config });
const body = renderToString(<NibProvider runtime={rt}><App /></NibProvider>);
const html = `<head><style id="nib">${collectNibStyles(rt)}</style></head><body>${body}</body>`;Two habits that bring the flash back#
- Deferring the CDN script. See the table above.
- Using a recipe name as a class.
className="btn"bypasses the engine; there is no.btn. Usenib="btn"oruseNib("btn").