Page Setup
Put the assets and inline scripts in <head>, and the main bundle just before </body>. The ?ver= query is a cache-busting stamp: change it whenever a bundle changes.
The version stamp
Change the ?ver= value every time you upgrade NDS. A stale stamp serves the old bundles from the browser cache.
Switching to a blocking critical stylesheet
The setup above is the gated one, and it is what this site runs. The inline style block draws the page shell so the page paints before critical CSS arrives, and the shell holds the layout until the real styles land. That block is the same shell this site serves, one rule per line so you can edit the colors.
Use a blocking stylesheet instead when a strict Content Security Policy cannot grant a nonce or a hash for that inline block. Delete the <style> block, then replace the preload link under it with a plain stylesheet link:
It blocks the first paint until critical CSS arrives, so the first thing on screen already has the real tokens and nothing flashes. Never remove the style block on its own: without the gate the page paints raw HTML first, then jumps when critical CSS lands.
Asset Files
What each bundle holds and how it loads.
| File | Contents | Loading |
|---|---|---|
nds.critical.min.css | Tokens, reset, fonts, hero, gate | Render-blocking (or async behind the gate) |
nds-main.min.css | All component and layout styles | Deferred; gates the page reveal |
nds-icons.min.css | UI icons (nds-icon) | Added by the loader once main CSS applies |
hgi-rounded-stroke-min.css | Content icon glyph map (hgi hgi-stroke). Its @font-face ships in the critical file, so this sheet can land late without a full relayout | Added by the loader at the reveal, after main CSS and the critical pass |
nds-accessibility.min.css | Accessibility panel and its mode overrides | Fetched by nds-accessibility.js itself once the panel arms — not linked in <head>. Optional; see Accessibility. |
nds-main.min.js | Loader and all component behavior | <script defer> before </body> |
nds-accessibility.min.js | Accessibility panel behavior | Fetched by nds-main.min.js on the first press of the accessibility button, or at load for a visitor with saved settings. No tag of its own. Optional |
Content Security Policy
NDS runs under a strict CSP. One inline script needs your permission. Everything else loads from your own origin and needs nothing.
A strict policy blocks inline code. NDS keeps its inline code to one small script, so you have one thing to allow. You allow it with a nonce or with a hash. Prefer the nonce whenever your server renders responses. Use the hash only on a static host: a hash must match the script's bytes exactly, and that match breaks easily.
| Part | What it needs |
|---|---|
Inline script in <head> (theme guard + deferred stylesheets) | A nonce or a hash |
Inline critical gate (<style>), unless you switched to the blocking stylesheet | A nonce or a hash in style-src |
Inline knobs on copied markup (style="--gap: …") | Move the knob to your own class. A style attribute needs 'unsafe-inline', and no nonce or hash can cover one |
| All stylesheets and script bundles | 'self' |
| Icon sheets the loader adds | Nothing — same origin as your other files |
UI icons (nds-icon) | img-src data: — each icon is an inline SVG mask |
A policy that covers all of it:
Then put the same value on the tag:
Your server must make a new random value for every response. A fixed value is not a nonce. It gives an attacker the same permission your own code has.
The main bundle needs the value too. The loader adds more script files at runtime, and it copies the nonce from the main bundle's tag onto each one. Without it, a nonce-only policy — one with no 'self' in script-src — blocks them, and the components they carry never start.
No server? Use a hash instead. A hash covers the script's contents between the tags, byte for byte — indentation, line endings, everything. A file saved with Windows CRLF line endings hashes differently from the same script saved with LF, and a formatter or minifier that touches the script kills the match too. Hash the contents only, never the tags. Take the SHA-256 of the script's contents, base64 it, and add 'sha256-…' to script-src. The browser tells you the right value for the bytes it actually served: load the page with the policy on, and the console error prints the hash it expected. Re-do this whenever anything edits the script.
Inline knobs under a strict CSP
A style attribute needs 'unsafe-inline'. No nonce and no hash can cover one. So under a strict policy every inline knob is dead: the value never applies, and the only warning is the browser's own CSP violation.
Find them all. Search your pages for style="--. Then move each knob to a class in your own stylesheet, which 'self' already allows. The markup keeps its NDS classes; you add one of your own.
Then set the knob in your stylesheet:
NDS components are not affected. Their JavaScript sets styles through the CSSOM, which no policy blocks. Only knobs you wrote into a style attribute need this treatment.
Frameworks that re-render the head
The head script runs once, at page load. It adds the stylesheet links, and NDS later stamps data-nds-loaded and data-nds-fonts-loaded on <html>. None of that is in the server HTML. A framework that diffs the <head> against the server HTML on navigation removes all of it, while the inline gate stays. The page then hides itself until a full reload. Turbo, htmx boost and Blazor enhanced navigation all work this way.
Keep those nodes out of the diff: mark the injected links and the two <html> attributes as permanent, in the way your framework offers. If it removes them anyway, run the deferred-styles loop again after each navigation and put the two stamps back. The component side of a route change is covered in Refresh.
Why the stylesheets look the way they do
Each deferred stylesheet ships as a rel="preload" link with a data-nds-defer mark. The preload downloads the file at the right priority without blocking render. The head script then adds a normal stylesheet link for it, which reuses that download. A more common way to defer CSS is an onload attribute on the link. NDS does not use one, because a nonce and a hash both cover a script element, and neither can ever cover an inline event handler. An onload attribute needs 'unsafe-inline', which defeats the policy. Moving the same work into a script element is what makes a strict CSP possible.
NDS needs JavaScript. The head script is what applies the deferred sheets, so a browser with JavaScript turned off loads no styles and shows a blank page. There is no fallback for this, and it is deliberate: the components need JavaScript to work at all.
The icon sheets load from nds-main.min.js for the same reason. That file is already allowed by 'self', so icons need no grant from you at all.