Manual Style Tokens Color tokens

Color tokens

elements man style/tokens/color Read as markdown

Color is defined in two steps: raw OKLCH ramps, then semantic tokens that point at them. App CSS reads the semantic tokens. Everything here lives in themes/default.css inside @layer tokens.

Why OKLCH

The ramps are authored in OKLCH so that the same numbered step reads at the same perceived weight across every ramp. --danger-500 and --success-500 sit at the same lightness, so a red badge and a green badge feel like the same object in different colors. A couple of brand-exact anchors are pinned as hex (--neutral-900 is the Elements navy #102C42 exactly).

The neutral ramp

A single cool, navy-tinted gray ramp (hue 240, low chroma), from --neutral-50 (near-white) to --neutral-950 (near-black), plus --white and --black. --neutral-900 is pinned to #102C42.

--white  --black
--neutral-50 --neutral-100 --neutral-200 --neutral-300 --neutral-400
--neutral-500 --neutral-600 --neutral-700 --neutral-800 --neutral-900 --neutral-950

The status ramps

Four ramps, each a full 50–950 scale on the same lightness curve as the neutral ramp:

  • --success-*: green, hue 150
  • --warning-*: amber, hue 75
  • --danger-*: red, hue 25
  • --info-*: cyan, hue 200 (deliberately distinct from a typical blue accent)

Semantic tokens

These are what you consume. Each flips automatically in dark mode.

Surfaces (--bg*), page and panel backgrounds:

Token Role
--bg Page background (white in light)
--bg-soft Slightly raised surface
--bg-muted Muted fill (code, chips)
--bg-panel Panel background
--bg-elevated Elevated surface (cards, popovers)
--bg-canvas Recessed canvas behind panels
--bg-inverse Inverted surface (<pre> blocks)

Ink (--ink*), text colors, high to low emphasis:

Token Role
--ink Primary text
--ink-soft Secondary text
--ink-dim Tertiary / captions
--ink-faint Placeholder-level
--ink-inverse Text on an inverse surface

Rules (--rule*), border colors:

Token Role
--rule Default border
--rule-soft Faint divider
--rule-strong Emphasized border

Accent (--accent*), the brand axis, monochrome by default:

Token Role
--accent The brand color (defaults to --neutral-900)
--accent-deep Hover / pressed / gradient end
--accent-soft Tinted accent backgrounds
--accent-ink Text/icons on an accent fill
--accent-edge Accent border on tinted surfaces

--accent points at --neutral-900 by default, so an unbranded app is black-and-white. Repointing this block is the entire branding API. See branding.

Status triples

Every status ramp collapses to a four-token set built for alerts, badges, and toasts (a soft background, an ink color, a solid color, and an edge):

.alert {
  background: var(--success-soft);
  color:      var(--success-ink);
  border:     1px solid var(--success);
}

The set exists for each status:

Solid Soft bg Ink Edge
--success --success-soft --success-ink --success-edge
--warning --warning-soft --warning-ink --warning-edge
--danger --danger-soft --danger-ink --danger-edge
--info --info-soft --info-ink --info-edge

The accent axis follows the same shape (--accent / --accent-soft / --accent-ink / --accent-edge), which is why callouts and pills have an is-accent variant alongside the status ones.

Note: the solid status colors are tuned for text-on-tint and borders, where the ink color is the readable foreground. A filled danger button is a separate case. It sets color: var(--white) on a --danger fill deliberately, rather than using --danger-ink (which in dark mode is a light text tint, not a fill foreground). Don't assume --{status}-ink is always a valid text color on a solid --{status} fill.

Focus

Focus is one ring spec, consumed by every focusable component:

Token Role
--focus The ring color (defaults to --accent)
--focus-ring The full box-shadow ring
--focus-ring-danger The ring for destructive controls
:focus-visible { box-shadow: var(--focus-ring); }

Dark mode

themes/default.css remaps every semantic token above under @media (prefers-color-scheme: dark) and again under :root[data-theme="dark"]. The ramps don't move; the semantic pointers do (--bg--neutral-950, --ink--neutral-50, and so on). Anything built from semantic tokens is dark-ready for free. See dark-mode.