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
Canonical family
bg-bg-basebg-bg-elevatedbg-bg-mutedbg-accent-basebg-success-bgbg-warning-bgbg-danger-bgbg-info-bg
HSL family
bg-backgroundbg-cardbg-mutedbg-primarybg-secondarybg-destructivebg-successbg-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.
| Utility | Intended role |
|---|---|
bg-bg-base | Main component surface. |
bg-bg-elevated | Raised or hovered surface. |
bg-bg-muted | Quiet inset area. |
bg-bg-overlay | Backdrop over other content. |
text-fg-base | Primary text. |
text-fg-muted | Supporting description. |
text-fg-subtle | Metadata or captions; check the actual background. |
text-fg-inverse | Text on a suitable inverted fill. |
border-border-base | Dividers and ordinary surface edges. |
border-border-strong | Stronger boundary treatment. |
border-border-input | Form-control outline. |
bg-accent-base with text-accent-fg | Primary 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:
| Utilities | Intended pairing |
|---|---|
bg-background and text-foreground | Page surface and primary text. |
bg-card and text-card-foreground | Card surface and text. |
bg-popover and text-popover-foreground | Menu or popover surface and text. |
bg-primary and text-primary-foreground | Primary fill and text. |
bg-secondary and text-secondary-foreground | Secondary fill and text. |
bg-muted and text-muted-foreground | Quiet fill and supporting text. |
bg-accent and text-accent-foreground | Highlighted surface and text. |
text-destructive | Error text or a destructive affordance. |
bg-success, bg-warning, bg-info | Status fills, each with a corresponding -foreground token. |
border-border, bg-input, ring-ring | Ordinary border, field fill, and focus-ring role. |
bg-sidebar, bg-tab | Navigation 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.