Design tokens
Everything visual in forte-ui resolves through a CSS custom property. Components never contain a hardcoded colour, size or duration — which is why re-pointing a token restyles every component at once, coherently. This page is the global token reference; the tables are generated from the shipped stylesheets at build time, so they cannot drift from what actually renders. Per-component knobs (--forte-button-*, --forte-dialog-*, …) are a separate mechanism documented on Styling components and on each component's own page.
How to override a token
Set it on :root, or on a theme scope to affect one subtree:
:root {
--forte-radius-control: var(--forte-radius-2);
--forte-shadow-2: 0 2px 8px oklch(0 0 0 / 0.12);
}Your CSS always wins. The library ships every rule inside @layer forte.*, and layered rules lose to unlayered author CSS regardless of specificity — no !important needed, ever.
Two things to know before overriding:
- A typo fails silently.
var(--forte-color-pannel)is not an error — the declaration simply becomes invalid at computed-value time and the element inherits instead. Check names against these tables. - Preset-owned tokens live on
:rootalone. Tokens marked rewritten by presets below are whatdata-forte-radiusanddata-forte-densityrewrite. Overriding one on:rootmoves the default, but any preset attribute on an ancestor still wins for its subtree.
Colour
The accent, secondary and gray ramps — --forte-accent-1…12, --forte-secondary-1…12, --forte-gray-1…12 — are derived from the seeds described on the Theming page, in both light and dark mode. Prefer changing the seed over overriding ramp steps: the derivation is what keeps the palette coherent and the contrast pairs certified.
Components consume the ramps through semantic aliases, which are the right level for surgical overrides — they re-derive per scope and follow light/dark automatically when you assign them a ramp step:
Surfaces, text and borders
| Token | Default | Notes |
|---|---|---|
--forte-color-background | var(--forte-gray-1) | |
--forte-color-border | var(--forte-gray-7) | retuned under prefers-contrast: more |
--forte-color-border-muted | var(--forte-gray-6) | retuned under prefers-contrast: more |
--forte-color-border-strong | var(--forte-gray-8) | retuned under prefers-contrast: more |
--forte-color-foreground | var(--forte-gray-12) | |
--forte-color-foreground-muted | var(--forte-gray-11) | retuned under prefers-contrast: more |
--forte-color-foreground-subtle | var(--forte-gray-10) | retuned under prefers-contrast: more |
--forte-color-overlay | var(--forte-gray-1) | |
--forte-color-panel | var(--forte-gray-2) | |
--forte-color-panel-active | var(--forte-gray-4) | |
--forte-color-panel-hover | var(--forte-gray-3) |
Brand
| Token | Default |
|---|---|
--forte-color-on-primary | oklch(0.995 0 0) |
--forte-color-on-secondary | oklch(0.995 0 0) |
--forte-color-primary | var(--forte-accent-9) |
--forte-color-primary-active | var(--forte-accent-11) |
--forte-color-primary-border | var(--forte-accent-7) |
--forte-color-primary-hover | var(--forte-accent-10) |
--forte-color-primary-soft | var(--forte-accent-3) |
--forte-color-primary-soft-active | var(--forte-accent-5) |
--forte-color-primary-soft-hover | var(--forte-accent-4) |
--forte-color-primary-text | var(--forte-accent-11) |
--forte-color-secondary | var(--forte-secondary-9) |
--forte-color-secondary-active | var(--forte-secondary-11) |
--forte-color-secondary-border | var(--forte-secondary-7) |
--forte-color-secondary-hover | var(--forte-secondary-10) |
--forte-color-secondary-soft | var(--forte-secondary-3) |
--forte-color-secondary-soft-active | var(--forte-secondary-5) |
--forte-color-secondary-soft-hover | var(--forte-secondary-4) |
--forte-color-secondary-text | var(--forte-secondary-11) |
Status
Status hues are fixed rather than seed-derived — a brand colour must not be able to make an error look reassuring. The aliases below are what components read; behind them sit fixed ramp steps (--forte-danger-2…11, --forte-success-*, --forte-warning-*, --forte-info-*) you can retune if your palette needs different alarm colours.
| Token | Default |
|---|---|
--forte-color-danger | var(--forte-danger-9) |
--forte-color-danger-border | var(--forte-danger-7) |
--forte-color-danger-hover | var(--forte-danger-10) |
--forte-color-danger-soft | var(--forte-danger-3) |
--forte-color-danger-text | var(--forte-danger-11) |
--forte-color-info | var(--forte-info-9) |
--forte-color-info-border | var(--forte-info-7) |
--forte-color-info-soft | var(--forte-info-3) |
--forte-color-info-text | var(--forte-info-11) |
--forte-color-on-danger | oklch(0.995 0 0) |
--forte-color-on-info | oklch(0.995 0 0) |
--forte-color-on-success | oklch(0.995 0 0) |
--forte-color-on-warning | oklch(0.145 0 0) |
--forte-color-success | var(--forte-success-9) |
--forte-color-success-border | var(--forte-success-7) |
--forte-color-success-soft | var(--forte-success-3) |
--forte-color-success-text | var(--forte-success-11) |
--forte-color-warning | var(--forte-warning-9) |
--forte-color-warning-border | var(--forte-warning-7) |
--forte-color-warning-soft | var(--forte-warning-3) |
--forte-color-warning-text | var(--forte-warning-11) |
Focus ring
The ring is two tones that contrast with each other, so one boundary always clears 3:1 against whatever sits behind the control (WCAG SC 1.4.11) — a single accent-coloured ring cannot promise that.
| Token | Default | Notes |
|---|---|---|
--forte-color-focus-ring | var(--forte-focus-ring-inner) | |
--forte-focus-ring-inner | light-dark(oklch(0.240 0 0), oklch(0.995 0 0)) | |
--forte-focus-ring-offset | 2px | |
--forte-focus-ring-outer | light-dark(oklch(0.995 0 0), oklch(0.178 0 0)) | |
--forte-focus-ring-width | 2px | retuned under prefers-contrast: more |
Spacing
An eight-step scale on a 4px base. Components use it for all internal padding and gaps.
| Token | Default |
|---|---|
--forte-space-1 | 0.25rem |
--forte-space-2 | 0.5rem |
--forte-space-3 | 0.75rem |
--forte-space-4 | 1rem |
--forte-space-5 | 1.5rem |
--forte-space-6 | 2rem |
--forte-space-7 | 2.5rem |
--forte-space-8 | 3rem |
Typography
| Token | Default |
|---|---|
--forte-font-mono | ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace |
--forte-font-sans | ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif |
--forte-font-size-1 | 0.75rem |
--forte-font-size-2 | 0.875rem |
--forte-font-size-3 | 1rem |
--forte-font-size-4 | 1.125rem |
--forte-font-size-5 | 1.25rem |
--forte-font-size-6 | 1.5rem |
--forte-font-weight-bold | 700 |
--forte-font-weight-medium | 500 |
--forte-font-weight-normal | 400 |
--forte-font-weight-semibold | 600 |
--forte-letter-spacing-normal | 0em |
--forte-letter-spacing-tight | -0.01em |
--forte-line-height-normal | 1.5 |
--forte-line-height-tight | 1.2 |
Radius
The numbered scale plus the three semantic radii components actually read. Prefer overriding the semantic ones — or better, a radius preset, which is the form that nests correctly.
| Token | Default | Notes |
|---|---|---|
--forte-radius-1 | 0.125rem | rewritten by the data-forte-radius presets |
--forte-radius-2 | 0.25rem | rewritten by the data-forte-radius presets |
--forte-radius-3 | 0.375rem | rewritten by the data-forte-radius presets |
--forte-radius-4 | 0.5rem | rewritten by the data-forte-radius presets |
--forte-radius-5 | 0.75rem | rewritten by the data-forte-radius presets |
--forte-radius-6 | 1rem | rewritten by the data-forte-radius presets |
--forte-radius-control | var(--forte-radius-4) | rewritten by the data-forte-radius presets |
--forte-radius-full | 9999px | rewritten by the data-forte-radius presets |
--forte-radius-pill | var(--forte-radius-full) | rewritten by the data-forte-radius presets |
--forte-radius-surface | var(--forte-radius-5) | rewritten by the data-forte-radius presets |
Controls and density
Control heights, padding and the surface metrics — the group the density presets rewrite. The --forte-app-bar-h-* heights are here too: an AppBar is exactly that tall at each size, and the token exists so the layout around one — a sticky sidebar's offset, a page's scroll-padding-top — can read it.
| Token | Default | Notes |
|---|---|---|
--forte-app-bar-h-lg | calc(4.25rem + 1px) | rewritten by the data-forte-density presets |
--forte-app-bar-h-md | calc(3.75rem + 1px) | rewritten by the data-forte-density presets |
--forte-app-bar-h-sm | calc(2.75rem + 1px) | rewritten by the data-forte-density presets |
--forte-control-gap | var(--forte-space-2) | rewritten by the data-forte-density presets |
--forte-control-h-lg | 2.75rem | rewritten by the data-forte-density presets |
--forte-control-h-md | 2.25rem | rewritten by the data-forte-density presets |
--forte-control-h-sm | 1.75rem | rewritten by the data-forte-density presets |
--forte-control-px-lg | var(--forte-space-5) | rewritten by the data-forte-density presets |
--forte-control-px-md | var(--forte-space-4) | rewritten by the data-forte-density presets |
--forte-control-px-sm | var(--forte-space-3) | rewritten by the data-forte-density presets |
--forte-list-item-py | var(--forte-space-2) | rewritten by the data-forte-density presets |
--forte-surface-p | var(--forte-space-5) | rewritten by the data-forte-density presets |
--forte-target-comfortable | 44px | |
--forte-target-min | 24px |
Elevation
| Token | Default | Notes |
|---|---|---|
--forte-glass-alpha | 0.72 | retuned under prefers-reduced-transparency |
--forte-scrim-blur | 4px | retuned under prefers-reduced-transparency |
--forte-scrim-color | light-dark(oklch(0 0 0 / 0.45), oklch(0 0 0 / 0.65)) | retuned under prefers-contrast: more · retuned under prefers-reduced-transparency |
--forte-shadow-1 | 0 1px 2px 0 light-dark(oklch(0 0 0 / 0.05), oklch(0 0 0 / 0.4)) | retuned under prefers-contrast: more |
--forte-shadow-2 | 0 1px 3px 0 light-dark(oklch(0 0 0 / 0.1), oklch(0 0 0 / 0.5)), 0 1px 2px -1px light-dark(oklch(0 0 0 / 0.1), oklch(0 0 0 / 0.5)) | retuned under prefers-contrast: more |
--forte-shadow-3 | 0 4px 6px -1px light-dark(oklch(0 0 0 / 0.1), oklch(0 0 0 / 0.5)), 0 2px 4px -2px light-dark(oklch(0 0 0 / 0.1), oklch(0 0 0 / 0.5)) | retuned under prefers-contrast: more |
--forte-shadow-4 | 0 12px 24px -6px light-dark(oklch(0 0 0 / 0.12), oklch(0 0 0 / 0.6)), 0 4px 8px -4px light-dark(oklch(0 0 0 / 0.1), oklch(0 0 0 / 0.5)) | retuned under prefers-contrast: more |
Motion
Durations, travels and scales. The reduced-motion behaviour is built into the tokens themselves — under the OS preference or a data-forte-motion attribute, travels collapse to 0px and scales to 1 while durations only shorten — so consuming these tokens gives you correct reduced-motion handling without writing a media query. The spring easings (--forte-ease-spring-*) are sampled damped-oscillator curves; each pairs with its matching --forte-duration-spring-*, and truncating a spring with a shorter duration looks broken.
| Token | Default | Notes |
|---|---|---|
--forte-duration-fast | 160ms | collapsed under reduced motion |
--forte-duration-instant | 1ms | collapsed under reduced motion |
--forte-duration-loop-pulse | 1600ms | collapsed under reduced motion |
--forte-duration-loop-spin | 800ms | collapsed under reduced motion |
--forte-duration-loop-sweep | 1400ms | collapsed under reduced motion |
--forte-duration-move | 220ms | collapsed under reduced motion |
--forte-duration-normal | 240ms | collapsed under reduced motion |
--forte-duration-slow | 400ms | collapsed under reduced motion |
--forte-duration-spring-bouncy | 730ms | collapsed under reduced motion |
--forte-duration-spring-gentle | 700ms | collapsed under reduced motion |
--forte-duration-spring-precise | 410ms | collapsed under reduced motion |
--forte-duration-spring-snappy | 400ms | collapsed under reduced motion |
--forte-pulse-dip | 1 | collapsed under reduced motion |
--forte-scale-enter | 0.95 | collapsed under reduced motion |
--forte-scale-exit | 1.02 | collapsed under reduced motion |
--forte-scale-press | 0.97 | collapsed under reduced motion |
--forte-spin-turn | 1turn | collapsed under reduced motion |
--forte-travel-lg | 16px | collapsed under reduced motion |
--forte-travel-md | 8px | collapsed under reduced motion |
--forte-travel-page | 32px | collapsed under reduced motion |
--forte-travel-sm | 4px | collapsed under reduced motion |
--forte-travel-xs | 2px | collapsed under reduced motion |
For tooling
This same inventory ships in the package as machine-readable JSON — every token with its default, its typed registration where one exists, and every overriding selector:
import tokens from "@forte-ui/react/docs-data/tokens.json";These tables are rendered from that file, as is anything else that needs the full token surface — theme editors, lint rules, design-tool sync.