Skip to content

Token Taxonomy

Six token families, one decision tree

Silvery ships six distinct token categories. Each has a different contract — picking the right one makes your UI adapt correctly across 84 schemes, four tiers, and every user's terminal. Picking the wrong one produces hardcoded-looking apps that don't respect user themes.

Sterling tokens

Silvery 0.20.0 ships Sterling as THE Theme. The $fg-accent / $fg-muted / $bg-selected tokens used below are Sterling's flat hyphen-keys. Migrating from pre-0.20.0? Legacy $tokens keep resolving through 0.20.x — see the migration map.

The decision tree, memorized:

  1. Want app identity?$brand
  2. Want a color for a tag / category / chart series / priority?$red, $blue, $green, … (one of 8)
  3. Want to signal state or validation?$fg-error, $fg-success, $fg-warning, $fg-info
  4. Want UI chrome (background, border, text)?$fg, $bg, $fg-muted, $border-default, $fg-accent, …
  5. Writing a syntax highlighter?$color0$color15
  6. Emphasizing something hierarchically? → typography preset (<H1>, <Small>, etc.)

Each branch below explains when it's the right answer.

1. App identity — $brand

The one color that's you. Your logo, your chrome, the signature accent that says "this is my app."

tsx
<Text color="$brand">MyApp v2.3</Text>
<Box borderColor="$brand">…</Box>
TokenResolves to
$brandApp's primary identity color. Defaults to scheme.primary (often the cursor color or the scheme's declared primary). Apps pin via <ThemeProvider tokens={{ brand: "#5B8DEF" }}>.
$brand-hover+0.04 L in OKLCH (+darken on light themes). For interactive hover states.
$brand-active+0.08 L in OKLCH. For active/pressed states.

Apple analogue: UIColor.tintColor / the per-app accent. Each app has ONE.

Don't use for: categorical tagging (use $red etc), semantic state (use $fg-error etc), or body text (use $fg).

$brand vs $fg-accent? $fg-accent is the scheme's canonical interactive color (Sterling derives it from scheme.primary or the scheme's accent slot). $brand is the app's identity. Apps that want their brand to dominate pin $brand; apps that want to blend in let $brand default to $fg-accent.

2. Categorical color ring — $red, $orange, $yellow, $green, $teal, $blue, $purple, $pink

Eight harmonious hues for tagging. Contrast-adjusted per scheme; visually balanced so no single hue pops more than another. These look great regardless of whether the user is in Dracula or Solarized.

tsx
// Tags
<Text color="$red">urgent</Text>
<Text color="$blue">research</Text>
<Text color="$green">done</Text>

// Calendar categories
<CalendarEvent color="$purple" title="Code review" />

// Chart series
<Series data={cpu} color="$teal" />
<Series data={mem} color="$orange" />
TokenResolves to
$redScheme's red, contrast-adjusted.
$orangeBlend of scheme's red + yellow.
$yellowScheme's yellow.
$greenScheme's green.
$tealBlend of scheme's green + cyan.
$blueScheme's blue (bright on dark themes).
$purpleScheme's magenta.
$pinkBlend of scheme's magenta + red.

Prior art: Apple's .systemRed/.systemIndigo/.systemTeal/…, Tailwind's bg-red-500, Material's red.500.

Don't use for: state semantics (use $fg-error). Don't hardcode tag colors in your app config — let the theme pick.

Why not use $color1 etc? $color0..$color15 are raw ANSI slots (user's terminal exactly). $red is contrast-adjusted so it's readable on every scheme. Use raw colors only for syntax highlighters where exact terminal parity matters.

3. Semantic state — $fg-error, $fg-success, $fg-warning, $fg-info

Colors that communicate meaning. The reader parses "yellow = caution" because the token says so semantically, not because the hue happens to be warning-shaped.

tsx
<Text color="$fg-error">Build failed</Text>
<Text color="$fg-success">Deployed</Text>
<Text color="$fg-warning">Deprecated API</Text>
<Text color="$fg-info">3 updates available</Text>

Status roles ship a full surface pair plus contrast-picked text-on-fill:

Status textStatus fillText on fillUse for
$fg-error$bg-error$fg-on-errordanger, validation errors
$fg-warning$bg-warning$fg-on-warningcaution, deprecations
$fg-success$bg-success$fg-on-successcompletions, confirmations
$fg-info$bg-info$fg-on-infoneutral info, tips

Plus bg-X-hover and bg-X-active for interactive surfaces. Note that text on a status role doesn't carry hover variants — fg-error-hover would be a category error (text on a status role isn't a link). For interactive text-color hover, use $fg-accent-hover.

When an app needs a red that's NOT an error (e.g. "delete button" or a red tag category): use $red or $brand. $fg-error is reserved for error state.

Visual state cue chain: don't rely on color alone. Pair with icons / attrs / text prefixes so colorblind users and monochrome terminals still convey state. See the Capability Tiers tier degradation section.

4. UI chrome — $fg, $bg, $fg-accent, $fg-muted, $border-default, and friends

The design-system scaffolding. Most component code uses these. If a token isn't in one of the other categories, it's here.

Root + text hierarchy

TokenMeaning
$fgDefault body text
$bgDefault background
$fg-mutedSecondary text (captions, hints, disabled)
$fg-on-inverseText on $bg-inverse (status bars, modal chrome)

Sterling collapses pre-0.20 $muted / $secondary / $disabledfg / $faint onto a single role: $fg-muted. The <Small> typography preset emits a pre-dimmed variant for fine print.

Surface stack

Every "surface" comes as a bg-X background paired with $fg (text) or $fg-on-X (text on filled surface).

tsx
<Box backgroundColor="$bg-surface-raised">
  <Text>Card content</Text>
</Box>

<Box backgroundColor="$bg-error">
  <Text color="$fg-on-error">Build failed</Text>
</Box>
SurfaceUse
$bg / $fgDefault root canvas
$bg-surface-subtleCards, list rows
$bg-surface-raisedDialogs, modals, raised panels
$bg-surface-overlayTooltips, dropdowns, overlays
$bg-inverse / $fg-on-inverseStatus bars, footers
$bg-muted / $fgInline muted chips, code

Accent (the canonical interactive role)

TokenMeaning
$fg-accentBrand emphasis text — headings, links, focus indicators, nav highlights
$bg-accentPrimary action button fill
$fg-on-accentText on $bg-accent (contrast-picked)
$fg-accent-hoverText-color hover (link hover) — Sterling's only role with fg.hover / fg.active
$bg-accent-hoverSurface hover on accent fill
$border-accentAccent-emphasized borders (focus rings, etc.)

$fg-accent vs $brand? $fg-accent is the scheme's canonical interactive color (derives from scheme.primary). $brand is the app's identity. Apps that want their brand to dominate pin $brand; apps that want to blend in let $brand default to $fg-accent.

TokenMeaning
$border-defaultDefault structural border — faint, not prominent (1.5:1 contrast target).
$border-focusFocused input border (3:1 WCAG 1.4.11).
$border-mutedFaint subdivider for dense layouts
$fg-linkHyperlink color — distinct from $fg-accent (often classic blue)

Selection + cursor

TokenMeaning
$bg-selectedCursor row, mouse selection, search match highlight
$fg-on-selectedText on $bg-selected (contrast-picked)
$bg-cursorCursor block/line color
$fg-cursorGlyph color under the cursor

5. Raw ANSI palette — $color0$color15

The user's terminal colors, verbatim. No contrast adjustment, no theming. What the user configured in their emulator is what you get.

tsx
<Text color="$color1">red</Text>  {/* scheme.red directly */}
<Text color="$color14">cyan bright</Text>  {/* scheme.brightCyan directly */}
IndexSlot
0black
1red
2green
3yellow
4blue
5magenta
6cyan
7white
8–15bright variants of the above

Use for: syntax highlighters (Prettier/tree-sitter-style) where exact terminal color parity matters. Git diff viewers. Anywhere the app explicitly wants the user's ANSI verbatim.

Don't use for: everyday UI. $red / $blue / $green are always preferable — they're contrast-adjusted, they don't look muddy on themes where the raw ANSI red is a subtle brown.

6. Typography presets — <H1>, <H2>, <H3>, <Muted>, <Small>, <Strong>, <Em>, <Code>, <Blockquote>

Not tokens per se, but the hierarchy primitives. Each preset bundles color + attrs so you don't hand-write <Text color="$fg-accent" bold>.

tsx
<H1>Page title</H1>
<H2>Section</H2>
<H3>Group</H3>
<Muted>Caption text</Muted>
<Small>Fine print</Small>
<Strong>Emphasis</Strong>
<Em>Italic emphasis</Em>
<Code>inline code</Code>
<Blockquote>pull quote</Blockquote>

Under the hood: these resolve to <Text variant="h1"> etc. against Sterling's theme.variants table. Apps extend the variant table via <ThemeProvider tokens={{ variants: { hero: { color: "$brand", bold: true } } }}>.

Decision tree, visualized

What color are you choosing for?

├── The app's identity / logo / signature chrome → $brand (+ -hover/-active)

├── A tag / category / chart series / priority level (no state meaning)
│    → $red / $orange / $yellow / $green / $teal / $blue / $purple / $pink

├── Communicating STATE (error, warning, success, info)
│    → $fg-error / $fg-warning / $fg-success / $fg-info

├── UI chrome (text, bg, borders, surfaces)
│    → $fg / $bg / $fg-muted / $border-default / $fg-accent / $bg-surface-* / …

├── Hierarchy (heading, fine print, emphasis)
│    → <H1>, <Small>, <Strong>, <Muted>, <Em>, <Code>, <Blockquote>

└── Raw terminal ANSI (syntax highlighter, user-exact parity)
     → $color0..$color15

Anti-patterns

  • $fg-error for anything that isn't literally an error — e.g. "delete" buttons, red tags. Use $red or $brand instead.
  • $color1 for everyday UI — it's the user's raw ANSI, not contrast-adjusted. Use $red unless you're writing a syntax highlighter.
  • $fg-accent for app brand$fg-accent is the scheme's canonical interactive color. For app brand, use $brand.
  • Hardcoded hex for a tinted surface — use $bg-surface-subtle / $bg-surface-overlay or mix($bg, $token, N%).
  • dim or dimColor anywheredim is a rendering detail. Use $fg-muted or <Small> instead. (See the Styling Guide for the full deprecation rationale.)
  • Legacy $tokens after migrating$primary, $muted, $selectionbg, etc. resolve through 0.20.x but go away in 0.21.0. Migrate now via the Sterling migration map.

Defining your own tokens

If you need a token that isn't covered above — priority levels, calendar-specific accents, brand sub-colors — use defineTokens():

tsx
import { defineTokens } from "silvery/theme"

const appTokens = defineTokens({
  "$priority-p0": { derive: (s) => s.brightRed },   // derivation — adapts to scheme
  "$priority-p1": { derive: (_, t) => t.red },
  "$priority-p2": { derive: (_, t) => t.orange },
  "$my-brand": { rgb: "#5B8DEF", ansi16: "brightBlue" },  // fixed brand
})

<ThemeProvider tokens={appTokens}>
  <App />
</ThemeProvider>

See the Custom Tokens guide for the full API.

See also