Knobs and derivation¶
The idea¶
A design system is a set of values that must move together. Most ship them as a few hundred
independent literals, which means they cannot move together — --shadow-md and --shadow-lg
being separate numbers is precisely why "less shadow" has no answer.
Here there are two layers:
- Knobs (
knobs.css, 27 values) - The overridable surface. Each is a single number or colour. Defaults are Tabler's measured values, so a project that overrides nothing looks like Tabler.
- Derived tokens (
derive.css, 128 values) - Everything else, computed from the knobs. Not meant to be overridden — overriding one breaks the link that keeps the system coherent.
Worked example: one colour¶
--ads-brand is one hex value. From it:
--ads-brand-hover: color-mix(in oklab, var(--ads-brand) 88%, black);
--ads-brand-active: color-mix(in oklab, var(--ads-brand) 76%, black);
--ads-brand-tint: color-mix(in srgb, var(--ads-brand) 10%, transparent);
--ads-brand-wash: color-mix(in srgb, var(--ads-brand) 8%, transparent);
--ads-brand-fill: color-mix(in srgb, var(--ads-brand) 8%, var(--ads-surface));
--ads-brand-line: color-mix(in srgb, var(--ads-brand) 20%, var(--ads-surface));
--ads-ring: color-mix(in oklab, var(--ads-brand) 35%, transparent);
Plus one that is not a mix, and is the interesting one:
--ads-brand-ink: light-dark(
color-mix(in oklab, var(--ads-brand), black calc(var(--ads-contrast) * 25%)),
color-mix(in oklab, var(--ads-brand) 55%, white));
-ink means the shade of this intent that reads as text. A brand colour chosen to look right
on white gives 7.0:1 there and 3.5:1 on a near-black surface — below the floor. So -ink is
lifted towards white in dark mode. Anything painting an intent as text uses -ink; anything
using it as a solid fill behind --ads-brand-fg uses the knob itself.
The same four shades exist for success, warning, danger, info and neutral.
Worked example: one number¶
Control heights are not literals. They are the line box plus a step of the space scale:
--ads-control-xs: var(--ads-line-control); /* 20px — a checkbox */
--ads-control-sm: calc(var(--ads-line-control) + var(--ads-space-1)); /* 24px — a chip */
--ads-control-md: calc(var(--ads-line-control) + var(--ads-space-3)); /* 32px — a small button */
--ads-control: calc(var(--ads-line-control) + var(--ads-space-5)); /* 40px — an input */
At the default knobs those are exactly Tabler's 20 / 24 / 32 / 40. Move --ads-space-scale and
they move with everything else — which is why the density knob reaches a button's height and
not only its padding.
Deliberate non-derivations¶
Two places refuse to derive, and the reasons are worth knowing.
There is no --ads-text-ratio. Tabler's type steps are 10/12/14/16/20/24, whose successive
ratios are 1.200, 1.167, 1.143, 1.250, 1.200 — not a geometric scale. A single ratio cannot
reproduce the spec, so the spec wins over the tidier abstraction and the multipliers live in
derive.css.
--ads-tap does not scale with density.
It equals --ads-control-sm at the default knobs, but it is stated independently on purpose.
The density knob is taste; turning taste down must not make a control too small for a finger. It
is in rem rather than px so a reader who enlarges their text gets larger targets with it —
the same person is asking for both.
The collapse trick¶
A few knobs are scalars that must be able to turn a feature off:
Zero works because the derivations multiply rather than switch. There is no if in CSS, and
adding one per feature would put the branching in every component instead of in one place.
Reading the tokens¶
Full list with defaults: Reference → Knobs and
Derived tokens. Both pages are generated from the stylesheet by
abilian-ds tokens, and a test asserts they are complete — a menu with something missing is
worse than no menu.