Skip to content

Theming it

Everything visual comes from 27 knobs. Override them on :root — after the stylesheet — and the rest of the system follows.

A rebrand

:root {
  --ads-brand: #c2410c;
}

That is the whole rebrand. Hover, active, tint, wash, border, focus ring and the readable text shade of the brand are all color-mix() derivations of that one colour, so they move with it and stay in proportion.

A denser or roomier product

:root {
  --ads-space-scale: 0.875;   /* compact */
  --ads-space-scale: 1.25;    /* comfortable */
}

Every padding, gap and margin follows — and so does every control height, because heights are derived from the line box plus a step of the space scale rather than written as literals. A density knob that reaches a button's padding but not its height is not a density knob.

Shape and depth

:root {
  --ads-radius-scale: 0;        /* square everything */
  --ads-radius-scale: 2;        /* twice as round */
  --ads-shadow-strength: 0;     /* flat design */
  --ads-shadow-strength: 2;     /* heavy */
  --ads-shadow-color: light-dark(rgb(40 20 10), rgb(0 0 0));   /* a warm shadow */
}

Shadow geometry is fixed; the strength knob multiplies the opacity of every layer. Colour is separate so a warm shadow stays possible.

Neutrals

Neutrals are a hue and a chroma multiplier, not thirty hex codes:

:root {
  --ads-neutral-hue: 260;       /* violet-leaning chrome */
  --ads-neutral-chroma: 0;      /* pure grey */
  --ads-neutral-chroma: 2;      /* twice the colour */
}

The per-step saturation is not flat — a good ramp gets more saturated as it darkens — so the knob scales that curve rather than replacing it.

Two themes on one page

The tokens are declared on :root and [data-ads-scope], so any subtree can carry its own:

<body>
  <!-- the product -->
  <div data-ads-scope style="--ads-brand: #7c3aed; --ads-radius-scale: 2">
    <!-- a white-labelled panel, with its own brand and shape -->
  </div>
</body>

Useful for an embedded partner panel, a preview pane in a theme editor, or a multi-tenant dashboard.

Accessible intents

:root {
  --ads-contrast: 1;
}

The reference's palette is the default because matching it is the default. It is also below WCAG AA when an intent colour is used as text: measured on its own tint, Tabler's success reads 2.48:1, warning 2.73:1, info 2.77:1, where the floor for body text is 4.5.

--ads-contrast: 1 pushes each intent's ink far enough to clear it — success 4.97, warning 5.39, info 5.44 — and leaves every fill, border and tint exactly where it was. So the design does not change; only the text does. It is a scalar rather than a switch, so a project can sit anywhere between the two.

What not to override

Derived tokens are fair game, but overriding one breaks the link that makes the system coherent:

/* Don't. --ads-space-4 is derived; now it no longer follows the scale. */
:root { --ads-space-4: 19px; }

If you find yourself reaching for a derived token, the honest options are to move the knob, or to add a token upstream. Both are cheaper than the fork.

The full list

Every knob, its default and what it reaches: Reference → Knobs.