Foundations
Tokens
The full reference. Four tiers, and the separation between them is the whole point of the file: a component may see tiers 2 and 3, and tier 1 never.
- 1. Ramps
- Raw steps. They do not change between themes
- 2. Semantics
- What a colour is for. One declaration through light-dark()
- 3. Roles
- Paddings and sizes by purpose. Density retunes these
- 4. Component
- --btn-bg and the like, 2–3 variables
Markup
<div class="inst-panel">
<div class="inst-panel-header">
<span class="inst-panel-title">The tiers</span>
</div>
<div class="inst-panel-body">
<dl class="inst-kv">
<dt>1. Ramps</dt><dd>Raw steps. They do not change between themes</dd>
<dt>2. Semantics</dt><dd>What a colour is for. One declaration through light-dark()</dd>
<dt>3. Roles</dt><dd>Paddings and sizes by purpose. Density retunes these</dd>
<dt>4. Component</dt><dd>--btn-bg and the like, 2–3 variables</dd>
</dl>
</div>
</div>
Contract
A component sees tiers 2 and 3 — the semantics and the roles. It never sees tier 1.
.my-thing {
/* tier 2: what the colour is for */
color: var(--text-primary);
/* tier 3: a padding by purpose */
padding: var(--pad-panel);
/* tier 4: the point of adjustment of the component itself.
A button has five of them: --btn-bg and its neighbours */
background: var(--btn-bg);
}
| What | Required | Why |
|---|---|---|
| Tiers 2 and 3 in a component | yes | Tier 1 is raw material: a ramp does not change between themes, and reaching for it hard-codes the light theme |
| One instance of every parameter | yes | A duplicate is a future divergence, and it arrives sooner than it seems |
| 2–3 variables per component | yes | Tier 4 is points of adjustment, not a copy of every property |
A value through light-dark() |
yes | A second theme as a separate block does not exist |
Accessibility
| The check | go -C tools run ./cmd/contrast reads the real tokens.css and resolves light-dark(), color-mix() and var() the way a browser does. A new pair of tokens is added there together with the token |
| Thresholds | Text 4.5:1, a mark 3:1 (against the surface and against the track), a load-bearing border 3:1 |
| Six themes | Every pair is checked in all six rather than in two |
Sizes in rem |
The browser's "default font size" setting works; the heights are applied as min-block-size |
| The floor of the type size | --text-2xs, 11px. There is no token below it |
Anatomy
Raw steps. They do not change between themes. A ramp is continuous by definition: a step the semantics has not taken is a reserve for the application and for charts rather than rubbish.
| Group | Tokens | Hue |
|---|---|---|
| Neutral | --n-0 … --n-14 |
--hue-neutral (75 warm / 250 cool) |
| Accent | --a-1 … --a-6 |
250° |
| Success | --ok-1 --ok-2 --ok-3 --ok-4 --ok-5 --ok-6 |
150° |
| Warning | --warn-1 --warn-3 --warn-4 --warn-5 --warn-6 |
85° |
| Error | --err-1 --err-3 --err-4 --err-5 --err-6 |
25° |
The statuses have no step 2: their roles are fixed (1 is the background in a light theme, 3 the text in a dark one, 4 the fill in a light one, 5 the text in a light one, 6 a reserve for charts), and a free step between 1 and 3 has not been wanted once. The lightness values and the analysis of the roles are in colour.
The hue knob
| Token | Value | What it does |
|---|---|---|
--hue-neutral |
75 | The cast of the neutral. 75 is warm, 250 cool. A chroma of 0.002–0.006: below the threshold of conscious notice |
Tier 2 — semantics
One declaration per token through light-dark(). A second block of tokens does
not exist, so there is nothing to diverge.
Surfaces
| Token | Light | Dark |
|---|---|---|
--surface-sunken |
--n-2 |
--n-14 |
--surface-page |
--n-1 |
--n-13 |
--surface-raised |
--n-0 |
--n-12 |
--surface-overlay |
--n-0 |
--n-11 |
--surface-field |
--surface-recessed, in both themes |
|
--surface-hover |
black 3.5% | white 4.5% |
--surface-active |
black 6.5% | white 8% |
--surface-selected |
--a-4 14%, in both themes |
|
--surface-recessed |
black 6% | white 5% |
--surface-recessed-hover |
black 2.5% | white 1.5% |
--surface-recessed-active |
black 7.5% | white 6.2% |
The last three are the recess of a control: the default button, a tag, the
fallback fill of a badge. Black laid over rather than a step of the ramp of its
own, and that is no trifle. A step sets lightness absolutely and therefore
knows one depth, while a control lies now on a page, now on a panel, now in a
modal — and it has to step away from each of them by the same amount. On a
raised panel --surface-sunken gave a drop of 0.087 instead of 0.033: the
panel is already a step higher, and the steps added up.
The direction depends on where we are on the ramp, as with
--surface-hover. In the light themes a control is recessed — a black film; in
the dark ones it is raised — a white one.
That is not a refusal of the drop but the only form of it available. A film
subtracts lightness, and at the bottom of the ramp there is nothing to subtract
from: the page of a dark theme is rgb(12,12,12), and even a solid black gives
1.073 against it against 1.142 in the light ones. The drop that is wanted
does not exist as a darkening. And the default button has its fill as its
only mark: there is no border and no shadow, the drop removed both.
The invariant is therefore stated without the word "depth": the surface of a control has to differ from its ground by a discernible step.
The sign of the film changes, the order of loudness does not. The middle
row serves two jobs at once: it is the hover of the default and the resting
fill of the soft weight inst-btn--soft. So it has to be quieter than the
default in both themes, otherwise the third step of the ladder sounds louder
than the second. Loudness here is the modulus of the step against the ground
rather than the alpha: in the light themes quieter means less black, in the
dark ones less white.
Three cmd/contrast pairs guard this: "a button against a panel", "a button
against a page" and "the ladder: soft against default".
Text
| Token | Light | Dark | Threshold |
|---|---|---|---|
--text-primary |
--n-12 |
--n-1 |
4.5:1 |
--text-secondary |
--n-9 |
--n-5 |
4.5:1 |
--text-muted |
--n-8 |
--n-6 |
4.5:1 |
--text-faint |
--n-7 |
--n-7 |
3:1 — not for text that gets read |
Borders
| Token | Light | Dark | Requirement |
|---|---|---|---|
--border-subtle |
black 7% | white 6% | Decorative |
--border |
black 12% | white 11% | Decorative |
--border-strong |
black 22% | white 20% | Decorative |
--border-control |
black 46% | white 38% | Load-bearing, 3:1 |
Accent
| Token | Light | Dark | Work |
|---|---|---|---|
--accent-text |
--a-5 |
--a-3 |
Text, 4.5:1 |
--accent-mark |
--a-4 |
--a-3 |
A mark with no label, 3:1 against the surface and the track |
--accent-bg |
--a-1 |
--a-4 15% |
A tinted background |
--accent-solid |
--a-4, in both themes |
The fill of a button, a lightness ceiling that depends on hue | |
--accent-on |
--n-0, in both themes |
The label on a fill | |
--accent-hover |
--a-5, in both themes |
It moves away from the colour of the label rather than towards it | |
--accent-border |
--a-4 |
--a-3 |
The border of an accented control |
--focus-ring |
--a-4 |
--a-3 |
The focus ring — the ordinary accent. What parts it from the fill it outlines is the 2px gap of outline-offset, not a colour chosen to dodge that one case |
Statuses
| Token | Light | Dark |
|---|---|---|
--ok-text |
--ok-5 |
--ok-3 |
--ok-bg |
--ok-1 |
--ok-4 16% |
--warn-text |
--warn-5 |
--warn-3 |
--warn-bg |
--warn-1 |
--warn-4 16% |
--err-text |
--err-5 |
--err-3 |
--err-bg |
--err-1 |
--err-4 16% |
There is no third token — a "fill" between the text and the background. There is one exception, the accent: it has a case the statuses do not, a fill under a white label.
Tone through an attribute
data-tone sets three variables, and a component reads them and knows nothing
else about the tone.
| Value | --tone-ink |
--tone-mark |
--tone-bg |
|---|---|---|---|
neutral |
--text-secondary |
--text-secondary |
--surface-sunken |
running |
--accent-text |
--accent-mark |
--accent-bg |
ok |
--ok-text |
--ok-text |
--ok-bg |
warn |
--warn-text |
--warn-text |
--warn-bg |
error |
--err-text |
--err-text |
--err-bg |
Charts
| Token | Hue | Light | Dark |
|---|---|---|---|
--chart-1 |
292° | L 0.520 | L 0.700 |
--chart-2 |
320° | L 0.600 | L 0.760 |
--chart-3 |
355° | L 0.550 | L 0.720 |
--chart-4 |
55° | L 0.650 | L 0.800 |
--chart-5 |
115° | L 0.580 | L 0.740 |
--chart-6 |
190° | L 0.620 | L 0.780 |
The rest
| Token | Light | Dark |
|---|---|---|
--track |
black 10% | white 16% |
--switch-track |
black 45% | white 36% |
--scrim |
black 32% | black 58% |
--shadow-color-near |
black 6% | black 30% |
--shadow-color-far |
black 10% | black 45% |
--shadow-popover |
two layers, short | |
--shadow-modal |
two layers, long |
The order of layers
| Token | Value |
|---|---|
--z-sticky |
10 |
--z-popover |
100 |
--z-modal |
1000 |
Tier 3 — roles
The one tier density retunes, and the one reason there are no constants in the
components. Declared through :where() — zero specificity, so an application
overrides without a fight. The values below are for the default density, and
the other two are in density.
Control heights and paddings
| Token | Value |
|---|---|
--control-h-xs |
20px |
--control-h-sm |
26px |
--control-h-md |
32px |
--control-h-lg |
38px |
--control-pad-sm |
--space-4 |
--control-pad-md |
--space-5 |
--control-pad-lg |
--space-6 |
Paddings by purpose
| Token | Value |
|---|---|
--pad-panel |
--space-5 |
--pad-card |
--space-6 |
--pad-cell-x |
--space-4 |
--row-pad-y |
--space-3 |
--gap-row |
--space-3 |
--gap-inline |
--space-4 |
--gap-section |
--space-7 |
--gap-section is the fourth step of the rhythm: the gap between sections,
where --gap-row works inside one. The value is not appointed but derived: it
is --gap-row raised by four steps of the scale. The scale is built so that +4
steps give exactly ×4 at every point — 4→16, 6→24, 8→32, 12→48, 16→64 — so the
ratio holds across all fifteen combinations of scale and density with no
separate fitting.
The role appeared after assembled screens were measured. Without it the tier stopped at 16px: the loudest thing available to part two blocks was of the same order as the gap inside a block — and the screen read as one continuous flow. The steps 24, 32, 48 and 64 meanwhile lay in the scale without a single role.
Glyphs and gutters
| Token | Value |
|---|---|
--size-dot |
6px |
--size-check |
15px |
--size-switch-w |
30px |
--size-switch-h |
18px |
--size-thumb |
14px |
--size-track |
4px |
--size-meter |
6px |
--size-spinner |
14px |
--size-icon |
18px |
--size-chevron |
10px |
--size-marker |
2px |
--size-gutter |
14px |
--size-indent |
14px |
--label-col |
104px |
Layout
| Token | Value |
|---|---|
--aside-w |
232px |
--container-max |
76rem |
--split-side |
18rem |
--split-main |
30rem |
--col-min |
260px |
Scale
Not tied to a theme and not retuned by density. The details are in typography and rhythm and shape.
Typography
| Group | Tokens |
|---|---|
| Families | --font-sans --font-mono |
| Sizes | --text-2xs --text-xs --text-sm --text-md --text-lg --text-xl --text-2xl |
| Weights | --weight-normal --weight-medium |
| Leading | --leading-tight --leading-ui --leading-code --leading-prose |
| Tracking | --tracking-tight --tracking-normal |
| Measure | --measure |
--measure is the limit on the length of a line, 68ch. The classical norm is
45–75 characters; 68 stands at the upper edge, because interface descriptions
are short and wrap rarely. The unit is ch rather than pixels: otherwise the
measure parts ways with the type size on each of the five steps of scale.
The role appeared after a measurement: 68ch was written as a literal in two
places, while the body of a banner had no measure at all and reached 105
characters on a container of 1440px. What is truncated is the body of the
banner rather than the banner: the ground of a page-level message runs the full
width, the text on it does not.
Space and shape
| Group | Tokens |
|---|---|
| The scale | --space-1 … --space-10 (2 · 4 · 6 · 8 · 12 · 16 · 24 · 32 · 48 · 64) |
| Radii | --radius-2xs 2 · --radius-xs 4 · --radius-sm 6 · --radius-md 8 · --radius-lg 12 · --radius-full. Controls take --radius-control-sm/md/lg, which travel with density; marks — anything below 8px — take --radius-mark, which does not |
| Lines | --hairline (0.5px from 1.5dppx, otherwise 1px) · --stroke 1px |
Motion
| Group | Tokens |
|---|---|
| Durations | --dur-1 80ms · --dur-2 140ms · --dur-3 220ms |
| Easing | --ease-out · --ease-in-out |
Tier 4 — the component
It lives beside the component rather than here. There is one rule: 2–3 variables, no more, and a variant is described by them rather than by rewriting the block.
.my-btn--accent {
--btn-fg: var(--accent-on);
--btn-bg: var(--accent-solid);
}
The list of a particular component's variables is on its page: the button has five, the table has its own.
Variants
| Attribute | color-scheme |
--hue-neutral |
And also |
|---|---|---|---|
| none | by the system | 75 | |
data-theme="light-neutral" |
light |
75 | --tint: 0 — the default, a pure grey |
data-theme="light" |
light |
75 | --tint: 2 — warm |
data-theme="light-cool" |
light |
250 | --tint: 2 — cool |
data-theme="dark-soft" |
dark |
75 | The stack one step up, plus the status labels |
data-theme="dark" |
dark |
75 |
Rules
| Rule | Consequence |
|---|---|
| A component sees tiers 2 and 3 | A --n-3 in a component is a hard-coded light theme; a 14px is a broken density |
| A tone that carries meaning has exactly two tokens | --*-text (the mark) and --*-bg (the background). A third did not pass the threshold on a track |
| Text and a mark are different foregrounds | --tone-ink holds 4.5:1, --tone-mark 3:1, but against two backgrounds |
| A load-bearing and a decorative border are different tokens | --border-control owes 3:1, --border does not |
Defaults are declared through :where() |
A default with the same specificity as an override is not a default |
| A new value is a role rather than a constant | Otherwise it will not survive compact |
| There is no such thing as a second accent tone | That is not a feature request but a signal that the meaning is carried by something else |
What to take and what not to
| Use | Take instead |
|---|---|
Tier 2 — the semantics: --text-primary, --surface-raised, --ok-text |
Tier 1 (--n-3, --a-4) — that is a hard-coded light theme. The ramps are analysed in colour |
Tier 3 — the roles: --pad-panel, --control-h-md, --size-dot |
A number in a component — it will break density, see density |
| Tier 4 — 2–3 variables of a component for a variant | A copy of the whole block of rules — a variant should be two lines, as on the button |
| A new parameter — as a role | A new constant — a constant will outlive you and break compact. The scales are in rhythm and shape |
| Overriding a role in an application | !important — the roles are declared through :where(), there will be no fight. An example is the form |
API
Listed by a generator reading src/tokens.css. The list is not typed by hand
and therefore cannot fall behind the sources.
| Name | Value | Where from |
|---|---|---|
| token | ||
--a-1 | oklch(0.960 0.018 215) | base +3: accent |
--a-2 | oklch(0.800 0.078 215) | base +3: accent |
--a-3 | oklch(0.700 0.100 215) | base +3: accent |
--a-4 | oklch(0.545 0.098 215) | base +3: accent |
--a-5 | oklch(0.450 0.082 215) | base +3: accent |
--a-6 | oklch(0.330 0.060 215) | base +3: accent |
--accent-bg | light-dark(var(--a-1), color-mix(in oklab, var(--a-4) 15%, transparent)) | one across every cell |
--accent-border | light-dark(var(--a-4), var(--a-3)) | one across every cell |
--accent-edge | color-mix(in oklab, var(--a-3) 50%, transparent) | one across every cell |
--accent-hover | var(--a-5) | one across every cell |
--accent-mark | light-dark(var(--a-4), var(--a-3)) | one across every cell |
--accent-on | var(--n-0) | one across every cell |
--accent-solid | var(--a-4) | one across every cell |
--accent-text | light-dark(var(--a-5), var(--a-3)) | base +1: theme |
--aside-w | 232px | base +4: scale |
--border | light-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11)) | one across every cell |
--border-control | light-dark(oklch(0 0 0 / 0.46), oklch(1 0 0 / 0.38)) | one across every cell |
--border-strong | light-dark(oklch(0 0 0 / 0.22), oklch(1 0 0 / 0.20)) | one across every cell |
--border-subtle | light-dark(oklch(0 0 0 / 0.07), oklch(1 0 0 / 0.06)) | one across every cell |
--chart-1 | light-dark(oklch(0.520 0.150 292), oklch(0.700 0.130 292)) | one across every cell |
--chart-2 | light-dark(oklch(0.600 0.150 320), oklch(0.760 0.120 320)) | one across every cell |
--chart-3 | light-dark(oklch(0.550 0.140 355), oklch(0.720 0.120 355)) | one across every cell |
--chart-4 | light-dark(oklch(0.650 0.120 55), oklch(0.800 0.110 55)) | one across every cell |
--chart-5 | light-dark(oklch(0.580 0.130 115), oklch(0.740 0.120 115)) | one across every cell |
--chart-6 | light-dark(oklch(0.620 0.110 190), oklch(0.780 0.100 190)) | one across every cell |
--col-min | 260px | base +4: scale |
--container-max | 76rem | base +4: scale |
--control-h-lg | 38px | base +14: density · scale |
--control-h-md | 32px | base +14: density · scale |
--control-h-sm | 24px | base +14: density · scale |
--control-h-xs | 20px | base +14: density · scale |
--control-pad-lg | calc(var(--control-h-lg) * var(--control-ratio-lg)) | one across every cell |
--control-pad-md | calc(var(--control-h-md) * var(--control-ratio-md)) | one across every cell |
--control-pad-sm | calc(var(--control-h-sm) * var(--control-ratio-sm)) | one across every cell |
--control-ratio-lg | 0.42 | one across every cell |
--control-ratio-md | 0.375 | one across every cell |
--control-ratio-sm | 0.31 | one across every cell |
--dur-1 | 80ms | one across every cell |
--dur-2 | 140ms | one across every cell |
--dur-3 | 220ms | one across every cell |
--ease-in-out | cubic-bezier(0.65, 0, 0.35, 1) | one across every cell |
--ease-out | cubic-bezier(0.22, 0.61, 0.36, 1) | one across every cell |
--err-1 | oklch(0.955 0.025 25) | one across every cell |
--err-2 | oklch(0.780 0.150 25) | one across every cell |
--err-3 | oklch(0.725 0.160 25) | one across every cell |
--err-4 | oklch(0.545 0.185 25) | one across every cell |
--err-5 | oklch(0.500 0.185 25) | one across every cell |
--err-6 | oklch(0.330 0.110 25) | one across every cell |
--err-bg | light-dark(var(--err-1), color-mix(in oklab, var(--err-4) 16%, transparent)) | one across every cell |
--err-mark | light-dark(var(--err-4), var(--err-3)) | one across every cell |
--err-text | light-dark(var(--err-5), var(--err-3)) | base +1: theme |
--focus-ring | light-dark(var(--a-4), var(--a-3)) | one across every cell |
--font-mono | ui-monospace, " , " , " , Consolas, " , monospace | one across every cell |
--font-sans | ui-sans-serif, system-ui, -apple-system, " , Roboto, " , Arial, sans-serif | one across every cell |
--gap-inline | var(--space-4) | base +14: density · scale |
--gap-row | var(--space-3) | base +14: density · scale |
--gap-section | var(--space-7) | base +14: density · scale |
--hairline | 1px | base +1: screen |
--hue-neutral | 75 | base +2: theme |
--label-col | 104px | base +4: scale |
--leading-code | 1.55 | one across every cell |
--leading-prose | 1.65 | one across every cell |
--leading-tight | 1.25 | one across every cell |
--leading-ui | 1.4 | one across every cell |
--measure | 68ch | one across every cell |
--n-0 | oklch(1.000 calc(0.002 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-1 | oklch(0.976 calc(0.003 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-10 | oklch(0.322 calc(0.005 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-11 | oklch(0.242 calc(0.004 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-12 | oklch(0.196 calc(0.004 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-13 | oklch(0.155 calc(0.003 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-14 | oklch(0.120 calc(0.003 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-2 | oklch(0.952 calc(0.004 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-3 | oklch(0.922 calc(0.004 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-4 | oklch(0.878 calc(0.005 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-5 | oklch(0.806 calc(0.006 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-6 | oklch(0.706 calc(0.006 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-7 | oklch(0.605 calc(0.006 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-8 | oklch(0.500 calc(0.006 * var(--tint)) var(--hue-neutral)) | one across every cell |
--n-9 | oklch(0.400 calc(0.006 * var(--tint)) var(--hue-neutral)) | one across every cell |
--ok-1 | oklch(0.955 0.025 150) | one across every cell |
--ok-2 | oklch(0.780 0.140 150) | one across every cell |
--ok-3 | oklch(0.660 0.130 150) | one across every cell |
--ok-4 | oklch(0.545 0.120 150) | one across every cell |
--ok-5 | oklch(0.500 0.120 150) | one across every cell |
--ok-6 | oklch(0.330 0.075 150) | one across every cell |
--ok-bg | light-dark(var(--ok-1), color-mix(in oklab, var(--ok-4) 16%, transparent)) | one across every cell |
--ok-mark | light-dark(var(--ok-4), var(--ok-3)) | one across every cell |
--ok-text | light-dark(var(--ok-5), var(--ok-3)) | base +1: theme |
--pad-block-y | var(--space-4) | base +14: density · scale |
--pad-card | var(--space-6) | base +14: density · scale |
--pad-cell-x | var(--space-4) | base +14: density · scale |
--pad-panel | var(--space-5) | base +14: density · scale |
--radius-2xs | 2px | base +4: scale |
--radius-control-lg | var(--radius-md) | base +14: density · scale |
--radius-control-md | var(--radius-md) | base +14: density · scale |
--radius-control-sm | var(--radius-sm) | base +14: density · scale |
--radius-full | 999px | one across every cell |
--radius-lg | 12px | base +4: scale |
--radius-mark | var(--radius-2xs) | one across every cell |
--radius-md | 8px | base +4: scale |
--radius-sm | 6px | base +4: scale |
--radius-xs | 4px | base +4: scale |
--region-edge-support | transparent | one across every cell |
--region-ground-support | var(--surface-recessed) | one across every cell |
--region-title-default | var(--text-xs) | one across every cell |
--region-title-lead | var(--text-sm) | one across every cell |
--region-title-support | var(--text-2xs) | one across every cell |
--row-pad-y | var(--space-3) | base +14: density · scale |
--scrim | light-dark(oklch(0 0 0 / 0.32), oklch(0 0 0 / 0.58)) | one across every cell |
--shadow-color-far | light-dark(oklch(0 0 0 / 0.10), oklch(0 0 0 / 0.45)) | one across every cell |
--shadow-color-near | light-dark(oklch(0 0 0 / 0.06), oklch(0 0 0 / 0.30)) | one across every cell |
--shadow-control | 0 1px 2px -1px var(--shadow-color-far), 0 1px 1px -1px var(--shadow-color-near) | one across every cell |
--shadow-modal | 0 16px 40px -8px var(--shadow-color-far), 0 4px 10px -4px var(--shadow-color-near) | one across every cell |
--shadow-popover | 0 4px 12px -2px var(--shadow-color-far), 0 2px 4px -2px var(--shadow-color-near) | one across every cell |
--size-check | 15px | base +14: density · scale |
--size-chevron | 10px | base +4: scale |
--size-dot | 6px | base +4: scale |
--size-gutter | 14px | base +14: density · scale |
--size-icon | 18px | base +4: scale |
--size-icon-lg | 24px | base +4: scale |
--size-icon-sm | 14px | base +4: scale |
--size-icon-xs | 12px | base +4: scale |
--size-indent | 14px | base +14: density · scale |
--size-marker | 2px | base +4: scale |
--size-meter | 6px | base +4: scale |
--size-spinner | 14px | base +14: density · scale |
--size-switch-h | 18px | base +14: density · scale |
--size-switch-w | 30px | base +14: density · scale |
--size-thumb | 14px | base +14: density · scale |
--size-tick | 3px | base +4: scale |
--size-track | 4px | base +4: scale |
--space-1 | 2px | one across every cell |
--space-10 | 64px | one across every cell |
--space-2 | 4px | one across every cell |
--space-3 | 6px | one across every cell |
--space-4 | 8px | one across every cell |
--space-5 | 12px | one across every cell |
--space-6 | 16px | one across every cell |
--space-7 | 24px | one across every cell |
--space-8 | 32px | one across every cell |
--space-9 | 48px | one across every cell |
--split-main | 30rem | base +4: scale |
--split-side | 18rem | base +4: scale |
--stroke | 1px | one across every cell |
--surface-active | light-dark(oklch(0 0 0 / 0.065), oklch(1 0 0 / 0.080)) | one across every cell |
--surface-field | var(--surface-raised) | one across every cell |
--surface-hover | light-dark(oklch(0 0 0 / 0.035), oklch(1 0 0 / 0.045)) | one across every cell |
--surface-inverse | light-dark(var(--n-12), var(--n-3)) | one across every cell |
--surface-inverse-soft | light-dark(var(--n-10), var(--n-4)) | one across every cell |
--surface-overlay | light-dark(var(--n-0), var(--n-11)) | base +1: theme |
--surface-page | light-dark(var(--n-1), var(--n-13)) | base +1: theme |
--surface-raised | light-dark(var(--n-0), var(--n-12)) | base +1: theme |
--surface-recessed | light-dark(oklch(0 0 0 / 0.060), oklch(1 0 0 / 0.050)) | one across every cell |
--surface-recessed-active | light-dark(oklch(0 0 0 / 0.075), oklch(1 0 0 / 0.062)) | one across every cell |
--surface-recessed-hover | light-dark(oklch(0 0 0 / 0.025), oklch(1 0 0 / 0.015)) | one across every cell |
--surface-selected | color-mix(in oklab, var(--a-4) 14%, transparent) | one across every cell |
--surface-sunken | light-dark(var(--n-2), var(--n-14)) | base +1: theme |
--switch-track | light-dark(oklch(0 0 0 / 0.45), oklch(1 0 0 / 0.36)) | one across every cell |
--tap-min | 24px | one across every cell |
--text-2xl | 1.75rem | base +4: scale |
--text-2xs | 0.6875rem | base +4: scale |
--text-faint | light-dark(var(--n-7), var(--n-7)) | one across every cell |
--text-lg | 1.125rem | base +4: scale |
--text-md | 1rem | base +4: scale |
--text-muted | light-dark(var(--n-8), var(--n-6)) | one across every cell |
--text-on-inverse | light-dark(var(--n-0), var(--n-13)) | one across every cell |
--text-primary | light-dark(var(--n-12), var(--n-1)) | one across every cell |
--text-secondary | light-dark(var(--n-9), var(--n-5)) | one across every cell |
--text-sm | 0.875rem | base +4: scale |
--text-xl | 1.375rem | base +4: scale |
--text-xs | 0.78125rem | base +4: scale |
--tint | 0 | base +2: theme |
--track | light-dark(oklch(0 0 0 / 0.10), oklch(1 0 0 / 0.16)) | one across every cell |
--tracking-normal | 0 | one across every cell |
--tracking-tight | -0.01em | one across every cell |
--warn-1 | oklch(0.960 0.040 85) | one across every cell |
--warn-2 | oklch(0.860 0.145 85) | one across every cell |
--warn-3 | oklch(0.760 0.140 85) | one across every cell |
--warn-4 | oklch(0.620 0.135 85) | one across every cell |
--warn-5 | oklch(0.520 0.135 85) | one across every cell |
--warn-6 | oklch(0.360 0.080 85) | one across every cell |
--warn-bg | light-dark(var(--warn-1), color-mix(in oklab, var(--warn-4) 16%, transparent)) | one across every cell |
--warn-mark | light-dark(var(--warn-4), var(--warn-3)) | one across every cell |
--warn-text | light-dark(var(--warn-5), var(--warn-3)) | base +1: theme |
--weight-medium | 600 | one across every cell |
--weight-normal | 400 | one across every cell |
--z-modal | 1000 | one across every cell |
--z-popover | 100 | one across every cell |
--z-sticky | 10 | one across every cell |
Why the kit works this way — the constitution · Open source under MIT, github.com/keshon/instrument