HTML & CDN

Use NibCSS on any HTML page with one script tag — no build tools, no npm, no framework.

3 min readv1

The shortest possible setup: one script tag on a page. The runtime scans the document, resolves every nib attribute it finds, and keeps watching for elements added later — including markup your own scripts inject.

This is the right choice for a landing page, a prototype, an email-style template, or any page that is not built by a bundler. When you want the classes compiled into the HTML instead, move on to the CLI — same config, same output, no runtime needed.

The one-tag version#

HTML
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <script src="https://cdn.jsdelivr.net/npm/@nibcss/runtime"></script>
</head>
<body nib="m=0; ftf=system-ui; p=40px; bgc=#f8fafc; dark:bgc=#0b0f19">
  <h1 nib="fts=32px; ftw=800; cl=#0f172a; dark:cl=#f8fafc">Hello, nib</h1>
  <button nib="p=10px 18px; bdrd=8px; bd=none; bgc=#4f46e5; cl=#fff; ftw=600; cur=pointer; hover:bgc=#4338ca">
    Click me
  </button>
</body>
</html>

With your own config#

Tokens, recipes, a custom prefix and dark mode belong in a config. On a page with no build step, hand it to the runtime with a plain script — before the runtime tag:

HTML
<head>
  <script>
    window.__NIB__ = {
      darkMode: "class",
      tokens: {
        color: { brand: "#4f46e5", ink: "#0f172a", surface: "#ffffff" },
        radius: { sm: "8px", md: "12px" },
      },
      components: {
        btn: "d=inline-flex; ali=center; g=8px; p=10px 18px; bdrd=$radius.sm; bd=none; bgc=$color.brand; cl=#fff; ftw=600; cur=pointer",
      },
    };
  </script>
  <script src="https://cdn.jsdelivr.net/npm/@nibcss/runtime"></script>
</head>

<button nib="btn">Save</button>

What the runtime does on the page#

Step

1

reads window.__NIB__ (or nib.config.js) and resolves your config over the defaults

2

scans the document for nib and data-nib attributes

3

writes the rules it needs into its own <style id="nib"> element

4

watches the DOM, so anything added later is styled too

Scoped to part of a page#

Embedding a widget into someone else's page? Give it its own runtime so your rules and theirs never collide:

HTML
<script type="module">
  import { NibRuntime } from "https://cdn.jsdelivr.net/npm/@nibcss/runtime/+esm";

  const widget = document.querySelector("#my-widget");
  const rt = new NibRuntime({ root: widget, prefix: "wgt-", tokens: { color: { brand: "#10b981" } } });
</script>

The browser API#

The global runtime is window.Nib:

Member

What it is

classesFor(attr)

the class string for an attribute, with its CSS inserted

toggleDarkMode(), darkMode.isDark()

the .dark class on <html>, persisted in localStorage

configure(partial)

change tokens, recipes or prefix and re-resolve the page

engine.config

the resolved config, as the browser has it

engine.stats()

rule and class counts

flush()

apply what the observer has queued, right now

NibRuntime

the class, for a second instance scoped to a subtree

HTML
Styled by the runtime, no build step
<div nib="d=flex; g=10px; ali=center; p=14px; bdrd=12px; bgc=#eef2ff; dark:bgc=#1e1b4b">
  <span nib="w=10px; h=10px; bdrd=999px; bgc=#4f46e5"></span>
  <span nib="fts=14px; ftw=600">Styled by the runtime, no build step</span>
</div>

Common pitfalls#

  • defer/async/type="module" on the runtime tag — the page paints unstyled first.
  • A custom prefix in the config but not in the browser: nothing on the page matches the stylesheet.
  • Expecting class="btn" to work — a recipe name is only a recipe inside nib, or through Nib.classesFor("btn").
  • Hand-written window.__NIB__ drifting from nibcss.config.ts — generate it with the CLI instead.

Next steps#