Color

Primitive scales feed semantic roles; components consume roles only. Ratios are computed live. Click any swatch to copy its variable name.

Scales

Primitive ramps from tokens.json, exported as --ds-color-* for reference and debugging. Never consume primitives in components. Neutral, brand, and accent use the 25–950 tonal shape when a step must keep one lightness identity across modes. Functional status colours use paired light/dark 1–12 scales when each step is designed for the mode it renders in and carries use rather than identity: 1–2 are app/subtle backgrounds, 3–5 component backgrounds, 6–8 borders, 9–10 solid fills, and 11–12 text. Only the active theme's status ramps are shown here; the theme toggle swaps them in place. Alpha ramps reproduce the neutral ramp as transparency — alpha.dark.N on white matches neutral.N, alpha.light.N on black does the same — and always use the current darkest/lightest neutral endpoints as their rgba bases.

Surfaces

Click a swatch to copy its variable. Values can coincide in one brand — page and panel are both white here — but the roles stay distinct so a rebrand can pull them apart without touching consumers.

ExampleTokenValueUsage
surface.canvasApp background; the base everything sits on
surface.pagePrimary content areas and forms
surface.panelCards, grouped content, navigation shells
surface.raisedElevated elements: popovers, menus, sticky bars
surface.overlayScrim behind modals and drawers

Colored surface context

A non-neutral surface is a complete context, not one background token. Map on-surface.primary, secondary, muted, hover, and active to that hue, then put ds-on-surface on the host. Topbar links, Ghost buttons, and Avatar consume the local aliases with neutral-canvas fallbacks.

Text

Emphasis grades static hierarchy — primary / secondary / muted are fixed levels, not interaction states. muted sits below AA contrast by design: placeholder-grade, never for content someone must read. link sits apart from that grading: it marks a destination rather than a level, and it is the one role here a brand is expected to re-point.

ExampleTokenUsage
Primary carries the message.text.primaryHeadings, body copy
Secondary supports it.text.secondarySupporting copy, labels, helper text, table headers
Muted whispers metadata.text.mutedPlaceholders, decorative metadata — never essential text
Link takes you somewhere.text.linkOptional branded link ink, applied through --ds-link-color only on a contrast-gated page or canvas; base Link inherits its parent

Border

subtle / default / strong grade static emphasis, like text. The same family also answers interaction through border.hover / border.active — shown in the Interaction table below. Every token belongs to exactly one axis.

ExampleTokenUsage
border.subtleDividers, table row separators
border.defaultControl and card outlines
border.strongHigh-emphasis outlines

Status

Each intent ships two emphases, both designed as bg + text pairs consumed whole, never one half — and the tier follows the feedback's lifetime: subtle (status.*.bg / .text) tints persistent in-flow state read in place (Alert, Badge), solid (status.*.solid-bg / .solid-text) carries transient or glanced feedback that must register instantly (Toast, solid Badge). Both pairs are mode-tuned from the matching functional scale; dark mode does not reuse an opaque deep light-theme swatch. Status stays functional even in a colorful brand: its ramps never double as the brand accent.

ExampleTokenUsage
SolidSubtlestatus.success.*Positive confirmation: saved, completed, online
SolidSubtlestatus.warning.*Reversible caution: pending, degraded, expiring
SolidSubtlestatus.error.*Failures and destructive outcomes
SolidSubtlestatus.info.*Neutral information and hints

Interaction

Rest / hover / active answer input — a separate axis from emphasis. control.*, border.*, and action.primary.* carry full triads for filled controls. The monochrome Primary and Danger buttons answer hover structurally by dropping to text-grade outlines, while secondary strengthens its outline and ghost derives a quiet translucent plate.

FamilyRestHoverActiveUsage
control.*Fills for menu items, tabs, list rows, ghost buttons
border.*Inputs and selectable cards answer interaction
action.primaryThe view's main action — one per view
secondary/currentColorSupporting actions inherit the surface pair; outline presence carries state
action.dangerDestructive actions

Contrast pairs

The approved text-on-surface combinations. The validator enforces these same pairs at build time; here they are computed live from the loaded theme, in both modes — each Example cell renders in its own pair, so what you read is the proof. When a pair fails after a palette change, adjust the ramp, not the role mapping: re-tune the derived step's color value (e.g. darken this brand's neutral.500) so the fix propagates to every consumer — never re-point the role to a different step, which silently breaks the hierarchy the roles encode.

ExampleRatioUsage
text.primary on surface.pageBody, headings
text.secondary on surface.pageSupporting text, labels
text.link on surface.pageLinks in prose
action.primary text on bgPrimary buttons
action.primary fg on surface.canvasText and border when a project deliberately outlines a primary state
action.danger text on bgDanger buttons
action.danger fg on bg-subtleDestructive menu rows, highlighted
text.primary on control.bg-activePressed fills, selected controls
status.success text on bgSuccess badges, alerts
status.warning text on bgWarning badges, alerts
status.error text on bgError badges, alerts
status.error.fg on surface.pageField error messages, error icons, invalid values
status.info text on bgInfo badges, alerts
status.success solid text on bgSolid success badges, toasts
status.warning solid text on bgSolid warning badges, toasts
status.error solid text on bgSolid error badges, toasts
status.info solid text on bgSolid info badges, toasts
focus.ring on surface.pageKeyboard focus indicator — non-text, min 3:1
status.error.border on surface.pageInvalid control borders — non-text, min 3:1