Skip to main content

Colours

Choose semantic surface, text, status, and chart colors that can adapt to light and dark themes.

4 min read

Choose a color token for its role: page surface, secondary text, input edge, or status. This lets the shared stylesheet adjust the appearance centrally without changing each component. Avoid introducing raw palette colors in new UI code when an existing semantic token fits.

Compare the two themes

Live example

Canonical family

  • bg-bg-base
  • bg-bg-elevated
  • bg-bg-muted
  • bg-accent-base
  • bg-success-bg
  • bg-warning-bg
  • bg-danger-bg
  • bg-info-bg

HSL family

  • bg-background
  • bg-card
  • bg-muted
  • bg-primary
  • bg-secondary
  • bg-destructive
  • bg-success
  • bg-warning

Use the header theme menu to switch between Light and Dark. The swatches retain their class names while their values change. Check text against the surface it actually sits on, rather than judging an isolated swatch.

Canonical semantic tokens

These tokens are declared in @theme in packages/ui/src/globals.css. The surface, text, border, and accent families have dark overrides.

UtilityIntended role
bg-bg-baseMain component surface.
bg-bg-elevatedRaised or hovered surface.
bg-bg-mutedQuiet inset area.
bg-bg-overlayBackdrop over other content.
text-fg-basePrimary text.
text-fg-mutedSupporting description.
text-fg-subtleMetadata or captions; check the actual background.
text-fg-inverseText on a suitable inverted fill.
border-border-baseDividers and ordinary surface edges.
border-border-strongStronger boundary treatment.
border-border-inputForm-control outline.
bg-accent-base with text-accent-fgPrimary neutral action fill and its foreground.

border-border-input resolves to the strong border in light mode and the base border in dark mode. Use the input primitive for its full outline/focus treatment rather than approximating the field with a plain divider border.

The canonical status tints include bg-success-bg, bg-warning-bg, bg-danger-bg, text-danger, and bg-info-bg. These values do not all have dark overrides. Pair them deliberately, or use a shared status component that already applies the intended treatment.

HSL-compatible aliases

The stylesheet also exposes the familiar HSL token family used by many existing components:

UtilitiesIntended pairing
bg-background and text-foregroundPage surface and primary text.
bg-card and text-card-foregroundCard surface and text.
bg-popover and text-popover-foregroundMenu or popover surface and text.
bg-primary and text-primary-foregroundPrimary fill and text.
bg-secondary and text-secondary-foregroundSecondary fill and text.
bg-muted and text-muted-foregroundQuiet fill and supporting text.
bg-accent and text-accent-foregroundHighlighted surface and text.
text-destructiveError text or a destructive affordance.
bg-success, bg-warning, bg-infoStatus fills, each with a corresponding -foreground token.
border-border, bg-input, ring-ringOrdinary border, field fill, and focus-ring role.
bg-sidebar, bg-tabNavigation and selected-tab surfaces.

Follow the existing file's family rather than partially converting its colors while adding an unrelated feature. The canonical and HSL names are supported vocabularies, not interchangeable values you can mix without checking the result.

Use chart-specific colors

Charts have chart-1 through chart-5 plus chart-success, chart-failure, chart-warning, chart-neutral, and chart-primary. CSS consumers can read var(--color-chart-1) and its siblings; Tailwind exposes the corresponding color utilities.

Use labels, shapes, or line patterns as well as color to distinguish meaningful series. Verify legends and tooltips in both themes, including small text and a single low-value data point.

Colour code

Code has roles of its own: --code-foreground and --code-token-keyword, -string, -string-expression, -constant, -function, -parameter, -comment, -punctuation, -link, -inserted, -deleted and -changed, with light and dark values in globals.css. Read-only code blocks and the code editor both read them, so a value has one colour wherever it shows, and a theme switch never highlights code again.

Every token clears 4.5:1 on the page, the code-block surface (bg-bg-elevated), a card and the muted surface, and on the editor's template tint; code-palette.test.ts checks each pair in both themes. The editor's chrome adds --code-line-number, --code-selection, --code-template-tint, --code-match and the problem underlines --code-squiggle-error, -warning and -info, which keep 3:1. Use the roles for code, not for other text.

Colour the lines of a workflow

A workflow canvas draws its lines from FLOW_EDGE_COLORS in @tale/ui/flow/edge-palette: --muted-foreground for the flow, --success for a Yes branch, --destructive only for the way into a failure, --foreground for emphasis and --info-foreground for activity. A No branch is amber, never red, through its own --flow-edge-negative: amber-700 in the light theme, because the warning amber is 2.2:1 on white and too faint for a line, and amber-500 in the dark one. Every line keeps 3:1 against the canvas in both themes, and shape carries the rest: dashed for an order, dotted for the end of a run.

Add a color only when the meaning is missing

First inspect the existing tokens and neighboring components. If a new semantic role is needed, add its token and intended theme treatment centrally. Record which foreground/background pairing it supports and check rest, hover, focus, selected, and error states.

A host accent is a separate runtime input used by participating components; see Theming. Neither an accent color nor a semantic token removes the need for the rendered accessibility review.