Skip to main content

What it governs

Every color that renders: surfaces, text, borders, icons, states, and status. Color in Titan is not a palette you pick from. It is a set of named intents that resolve to different values depending on mode. Color is the largest foundation — 97 of the 244 tokens on theme.tokens are color, and every one is token-bound rather than an untokenised literal.

Vocabulary

The colour vocabulary is fully specified. Four axes apply.
Sourced from Invoca’s design-token naming specification, confirmed by its owner as the baseline the design language is being built on. This is the vocabulary of record — not inferred from token names.
alt is a state, not a shade. background-error-alt is the alternate state of the error background — the tinted partner to the bold fill — sitting on the same axis as hover and pressed. Reading it as “a lighter error” is the most common misuse.invert sits on two axes — a prominence (text-invert, for content on an inverted surface) and a state. Read it from position in the name.
Two terms overlap and the spec does not separate them. neutral and disabled both appear as purposes, and background-neutral and background-disabled resolve to the same value — see TITAN-GAP-04.

The model

Titan’s colors are authored in three tiers as DTCG JSON, and flattened at build time.
Component code reaches the semantic tier, which is flat: you write background-selected, not the alias chain behind it.
The primitive tier is published as --titan-color-* and exists to be aliased, never referenced directly. The component tier is where Titan’s stated model and its source disagree — see TITAN-DIV-01. The same name resolves to a different value depending on the active color scheme. That resolution is the entire mechanism — there is nothing to configure and nothing to opt into.
Every color token also ships a …Channel companion — background-selectedChannel is "212 224 254", the space-separated RGB triplet. They exist so transparency can be applied to a color internally. Do not use them directly — deriving a color by applying alpha to a channel is the opacity anti-pattern TITAN-COLOR-06 prohibits.

The naming pattern

Four families, and two modifiers that appear throughout. Learning the modifiers is most of learning the system.
-alt is not “a lighter version.” It is the contrast-carrying partner, and its direction depends on the family. background-success-alt is a pale tint (#d5f2de); text-success-alt is nearly black-green (#0d5400). They are designed to be used together. Reaching for a plain status token where an -alt belongs is the most common source of contrast failures in Titan — see TITAN-COLOR-07.

Choosing a token

This is the section to read. The left column is what you are trying to express — in your words, not the system’s.

Surfaces

Text

Borders

Border widths are separate tokens:
border-width-md and border-width-emphasis are both 2px, but they are not the same token. Source records border-width-emphasis as the emphasized width for buttons, filter outlines, and slider thumbs; border-width-md records nothing. They also differ structurally — border-width-md aliases the primitive scale, while border-width-emphasis is a literal 2px declared at the semantic tier.Because the values are identical, the distinction is invisible on screen. See TITAN-GAP-04.

Status

Four statuses. A fifth invented locally is not a status — it is an unmet need, and it belongs in a token proposal. Each status has two complete treatments. Pick one and use it consistently.
The bold-fill treatment fails AA for success and warning. text-invert on background-success measures 2.41:1; on background-warning, 2.74:1. Both are far below the 4.5:1 minimum.Use the tinted treatment for anything containing text. Seven of its eight mode combinations pass AA outright. Full measurements in Accessibility.
Color is never the sole carrier of meaning — see TITAN-COLOR-03. Every status pairs a color with an icon and text. Roughly 1 in 12 men has some form of color vision deficiency; a red-only error state is invisible to them, and to anyone in bright sunlight or on a poorly calibrated display.

Token reference

The primitive tier

Tier 1 exists and is published — 106 raw ramp values as --titan-color-* custom properties. Semantic tokens alias into it; component code never touches it. Nine ramps run from 0 (lightest) to 100 (darkest) in steps of 10. Each cell is the value of color.{ramp}.{step}, so color.green.50 is #2cbf58. Values are identical in light and dark mode. Seven named values sit outside the ramps: Remember TITAN-COLOR-01: these hex values are for reference and design handoff, never for component code. If you need one of these colors, use the semantic token that aliases it.

Every semantic color, with its alias chain

Interaction states

The state tokens, grouped by the state they express. Each is a normal semantic color token — there is no separate state mechanism. Hover is not available on touch. Never let hover color be the only signal that something is interactive — see TITAN-COLOR-09.

Surface layering

Proposed, pending sign-off — see Open issues for what that leaves unsettled. Titan does not specify what happens below the raised level. A card inside a card has no defined surface, and inventing one locally is how a layering model stops being one.

Constraints

Accessibility

Full measured tables live in Accessibility. The headlines:
  • text-success (2.41:1) and text-warning (2.74:1) fail AA on all light surfaces. Use the -alt variants.
  • text-info and text-error pass on white but fail on the page canvas.
  • There is no semantic focus token. Three component-scoped rings ship — form-field-focus-ring, form-field-focus-ring-error, data-grid-focus-ring — each authored independently against a primitive. Nothing supplies a focus ring for the general case, so every other component decides for itself. See TITAN-DIV-02.
  • Disabled controls are exempt from contrast minimums, which is exactly why disabled state must never be the sole signal.
  • Do not rely on hover color alone to indicate interactivity; it does not exist on touch.

Known issues

Color: open issues

Divergences, open decisions, and undocumented gaps for Color.
34 core components consume this foundation — derived from token usage in components/core/src/components, not assumed: Accordion · AddList · Badge · Button · ButtonGroup · Card · Checkbox · Chip · CircularProgress · DataGrid · DatePicker · Dropzone · Footer · Form · FormInput · Header · List · LoaderOverlay · Menu · Modal · Progress · Radio · SearchableSelect · Select · Slider · Tabs · TextField · TextFieldWithSearchIcon · TextLink · Tile · Toast · Toggle · Tooltip · Typography

Why it works this way

Semantic tokens are named for intent, not appearance. background-selected rather than background-light-blue. Appearance names become lies the moment a second mode exists — and they were already lying to the person reading the code, who wants to know why the surface is that color. Two treatments per status, not one. A single status color cannot serve both a 12px badge and a paragraph of error text: what is legible as a small saturated mark is unreadable as a text color, and what carries text contrast is too dark to read as “success” at a glance. The plain/-alt split is that problem solved — provided the pairs stay paired. Four statuses, not six. Systems that add “neutral,” “pending,” and “in progress” as distinct status colors find that no user can distinguish them without reading the label — at which point the label is doing the work and the color is decorative. Four is the number that survives being squinted at.
Last modified on September 7, 2026