Theme navigation

Color

Color

Components consume semantic colors named by purpose. Those colors map to different primitives in light and dark themes, so switching themes changes values without changing class names.

Interface and surfaces

Page background--color-bg

Light gray-50 · Dark gray-950

Card surface--color-surface

Light white · Dark gray-900

Hovered surface--color-surface-hover

Light gray-100 · Dark gray-800

Recessed track--color-track

Light gray-200 · Dark gray-950

The groove behind segmented controls (Tabs solid, Segmented). It is defined by a relationship rather than a fixed grey: always one step deeper than the card surface, and in both themes the raised pill must read as closer to the viewer. Do not substitute the hovered surface — in light mode it is only 3.3% lighter than white, and in dark mode it is lighter than the pill, which inverts the elevation.

Subtle background--color-subtle

Light gray-100 · Dark gray-800

Static area backgrounds: grouping containers, board columns, swim lanes, note strips. It currently resolves to the same value as the hovered surface but means something different — writing hover: for a permanent background is a semantic mismatch.

Subtle background (shadcn alias)--color-muted

Light gray-100 · Dark gray-800

Same value and meaning as the subtle background; the name exists to match the shadcn/ui vocabulary, where --muted is a weak background. Before 0.28.0 this name held the secondary text colour — the exact opposite of the ecosystem — and it has since been renamed to the secondary text token below.

Borders and dividers--color-border

Light gray-200 · Dark gray-800

Hairline border--color-hairline

Light transparent · Dark gray-800 (same as border)

Use only with border-* on elevated surfaces. It is transparent in light mode; text-, bg-, or fill- would become invisible, so use border or muted-foreground for fill and text.

Text

Primary text--color-foreground

Light gray-900 · Dark gray-50

Secondary text--color-muted-foreground

Light gray-600 · Dark gray-400

Descriptive copy, timestamps, placeholders, disabled labels. Named --color-muted before 0.28.0.

Brand and emphasis

Primary--color-primary

Light brand-700 · Dark brand-400

The matching text colour is -foreground, but it only matches this solid fill: in light mode it is plain white. Painting a tint of your own (-subtle, or /12) and reaching for text-x-foreground by naming intuition gives you white on white and the text disappears — while dark mode looks right, so whoever develops in dark mode never sees it. On a tint the text colour is the semantic colour itself: solid = bg-x + text-x-foreground, soft = bg-x-subtle + text-x, outline = border-x + text-x.

Primary hover--color-primary-hover

Light brand-500 · Dark brand-500

Every semantic hover token picks the step between the light and dark values, so it moves one step towards lower contrast in both themes.

Primary tint--color-primary-subtle

Light brand-50 · Dark surface + 16% brand-400

Tinted backgrounds: notice bars, selected rows, Tag and Badge fills, the active sidebar item. The direction flips in dark mode — instead of getting lighter, the semantic colour is mixed into the surface, so the fill stays deeper than the surface and only picks up the hue. Do not derive these by mixing the accent towards white; that is wrong in dark mode.

Primary tinted border--color-primary-border

Light brand-200 · Dark surface + 34% brand-400

Focus ring--color-ring

Light brand-500 · Dark brand-400

Status

Danger--color-danger

Light danger-700 · Dark danger-400

The matching text colour is -foreground, but it only matches this solid fill: in light mode it is plain white. Painting a tint of your own (-subtle, or /12) and reaching for text-x-foreground by naming intuition gives you white on white and the text disappears — while dark mode looks right, so whoever develops in dark mode never sees it. On a tint the text colour is the semantic colour itself: solid = bg-x + text-x-foreground, soft = bg-x-subtle + text-x, outline = border-x + text-x.

Danger hover--color-danger-hover

Light danger-500 · Dark danger-500

Danger tint--color-danger-subtle

Light danger-50 · Dark surface + 16% danger-400

Tinted backgrounds: notice bars, selected rows, Tag and Badge fills, the active sidebar item. The direction flips in dark mode — instead of getting lighter, the semantic colour is mixed into the surface, so the fill stays deeper than the surface and only picks up the hue. Do not derive these by mixing the accent towards white; that is wrong in dark mode.

Danger tinted border--color-danger-border

Light danger-200 · Dark surface + 34% danger-400

Success--color-success

Light success-700 · Dark success-500

The matching text colour is -foreground, but it only matches this solid fill: in light mode it is plain white. Painting a tint of your own (-subtle, or /12) and reaching for text-x-foreground by naming intuition gives you white on white and the text disappears — while dark mode looks right, so whoever develops in dark mode never sees it. On a tint the text colour is the semantic colour itself: solid = bg-x + text-x-foreground, soft = bg-x-subtle + text-x, outline = border-x + text-x.

Success hover--color-success-hover

Light success-600 · Dark success-600

Success tint--color-success-subtle

Light success-50 · Dark surface + 16% success-500

Tinted backgrounds: notice bars, selected rows, Tag and Badge fills, the active sidebar item. The direction flips in dark mode — instead of getting lighter, the semantic colour is mixed into the surface, so the fill stays deeper than the surface and only picks up the hue. Do not derive these by mixing the accent towards white; that is wrong in dark mode.

Success tinted border--color-success-border

Light success-200 · Dark surface + 34% success-500

Warning--color-warning

Light warning-700 · Dark warning-500

The matching text colour is -foreground, but it only matches this solid fill: in light mode it is plain white. Painting a tint of your own (-subtle, or /12) and reaching for text-x-foreground by naming intuition gives you white on white and the text disappears — while dark mode looks right, so whoever develops in dark mode never sees it. On a tint the text colour is the semantic colour itself: solid = bg-x + text-x-foreground, soft = bg-x-subtle + text-x, outline = border-x + text-x.

Warning hover--color-warning-hover

Light warning-600 · Dark warning-600

Warning tint--color-warning-subtle

Light warning-50 · Dark surface + 16% warning-500

Tinted backgrounds: notice bars, selected rows, Tag and Badge fills, the active sidebar item. The direction flips in dark mode — instead of getting lighter, the semantic colour is mixed into the surface, so the fill stays deeper than the surface and only picks up the hue. Do not derive these by mixing the accent towards white; that is wrong in dark mode.

Warning tinted border--color-warning-border

Light warning-200 · Dark surface + 34% warning-500

Info--color-info

Light info-700 · Dark info-400

Hints, explanations and neutral notices, deliberately separate from the primary colour: primary means “this is actionable”, info means “this is an explanation”. Both are blues but 30° apart in hue — borrowing the primary colour for notices spends the scarcest attention budget on the page on descriptive copy. The matching text colour is -foreground, but it only matches this solid fill: in light mode it is plain white. Painting a tint of your own (-subtle, or /12) and reaching for text-x-foreground by naming intuition gives you white on white and the text disappears — while dark mode looks right, so whoever develops in dark mode never sees it. On a tint the text colour is the semantic colour itself: solid = bg-x + text-x-foreground, soft = bg-x-subtle + text-x, outline = border-x + text-x.

Info hover--color-info-hover

Light info-500 · Dark info-500

Info tint--color-info-subtle

Light info-50 · Dark surface + 16% info-400

Tinted backgrounds: notice bars, selected rows, Tag and Badge fills, the active sidebar item. The direction flips in dark mode — instead of getting lighter, the semantic colour is mixed into the surface, so the fill stays deeper than the surface and only picks up the hue. Do not derive these by mixing the accent towards white; that is wrong in dark mode.

Info tinted border--color-info-border

Light info-200 · Dark surface + 34% info-400

Chart colors

Colors for data series, consumed directly through var(). Light and dark themes tune luminance independently for contrast.

1
2
3
4

Semantic color ramps

Steps 50-300 are the tints used for notice bars, selected rows and Tag fills; 400-700 are the accent steps. The tints are hand-tuned in OKLCH rather than derived with mix(): sRGB interpolation shifts the hue, and every project picks a different percentage — yet tints cover the largest area of an admin UI.

--brand-*
50100200300400500600700
--danger-*
50100200300400500600700
--success-*
50100200300400500600700
--warning-*
50100200300400500600700
--info-*
50100200300400500600700

Primitive gray scale

The shared palette in primitives.css does not change with the theme. Semantic colors are derived from it.

50100200300400500600700800900950