Color anatomy
Saturated colors
Saturated colors infuse meaning into an experience, highlight UI, or create associations between similar elements. Nine hue families, each on a 10-step ramp.
Neutral colors
Neutral colors apply to most backgrounds, text, and shapes. They don't typically carry meaning, though they can imply states like disabled.
The same primitive ramp shown on a light surface and a dark surface — what shifts is which step the theme treats as "ground" and which it treats as "type."
Alpha colors
Alpha colors carry varying levels of transparency. They let UI adapt to whatever surface or imagery sits beneath them — scrims over photography, hover overlays on colored panels, soft borders that pick up the surrounding hue.
If you aren't using design tokens, the color palette page has hex codes and RGBa values for every step.
Applying color with tokens
Every token starts with the prefix --color- followed by the property it applies to (background, text, border, icon), then the role and any modifier — emphasis level, interaction state.
css
/* Anatomy */
--color-{property}-{role}[-{emphasis}][-{state}]
/* Examples */
--color-background-danger
--color-background-danger-bold
--color-background-danger-bold-hovered
--color-text-success
--color-border-discovery-subtle
--color-icon-disabledReach for tokens, not primitives
Components consume --color-background-success, not --color-green-500. Primitives are the source of truth for the palette, but only semantic tokens cross the boundary into components — so when light/dark themes diverge, components don't need to change.
Color roles
Color roles describe the intention behind a color. Roles apply across background, text, border, and icon — pick the role first, then let the token system pick the right weight for the property.
Flatpay has no brand color. Primary actions are charcoal; chroma enters only when a state earns it.
- primary
Standard body copy and the chrome that frames the system — the default text emphasis.
- secondary
Supporting information — captions, descriptions, metadata. One step quieter than primary.
- tertiary
Placeholder text in inputs and the lowest-priority labels. Used sparingly — it's the quietest text on a surface.
- disabled
Inert affordances — disabled buttons, fields the user can't edit. Not meant to be read clearly.
- inverse
Text and surfaces that sit on top of bold-emphasis backgrounds.
- information
Informative UI — info icons, links, neutral status, and 'in progress' affordances.
- success
Positive outcomes — completed payouts, demo-ready prototypes, healthy services.
- warning
Caution before failure. Pending decisions, near-threshold metrics, delayed batches.
- danger
Destructive actions and serious errors. Declined transactions, killed prototypes.
- discovery
What's new — beta surfaces, onboarding, lab tags, and content the user hasn't seen before. Blurple lives here, not at the top of the action hierarchy.
- accent
Categorical hues without semantic meaning — neutral, pink, blurple, natural, and the alternate accents you'd swap freely on a chart or illustration.
Emphasis levels
Emphasis is a layering property of accent backgrounds — not a modifier you apply to every role. Each accent has three stacked surfaces: subtlest opens a surface, subtler is a card on that canvas, and subtle is an inner highlight on top of subtler. Never open a surface on subtle. Text has no layering — it has its own hierarchy (primary, secondary, tertiary), shown in the roles above.
background-accent-green-subtlest · canvas
background-accent-green-subtler · card on canvas
background-accent-green-subtle · inner highlight
See accent palettes for the full layering rule and worked examples across every accent hue.
Interaction states
Interactive surfaces resolve through three states: default, hover, and active. Pair them with an action level — primary, secondary, or tertiary — and components just request the state they need.
tsx
<button
className="bg-[var(--background-interactive-primary-default)] hover:bg-[var(--background-interactive-primary-hover)] active:bg-[var(--background-interactive-primary-active)]"
>
Approve
</button>--background-disabled and --border-focus live as standalone tokens — they apply across every action level rather than vary by it.
Accessibility
Every text token in the system meets WCAG AA contrast against its intended background — 4.5:1 for body, 3:1 for large text and non-text UI elements. The bold-emphasis tokens are calibrated so white text remains legible on top.
Don't carry meaning in color alone
Pair every status color with an icon, label, or shape. A red dot next to "Killed" communicates twice — once for sighted users, once for everyone else.
Designing in dark mode
Dark mode reuses the same semantic tokens — only the primitive values they map to change. If you're consuming tokens correctly, the only thing that should change in dark mode is the underlying CSS variable. Components don't branch on theme.
Status
Dark-mode token mappings are still being calibrated. Today, components ship with light-only values; the full .dark override lands with the next theme pass.
Related
Page history
4 revisions- DocumentedDerek Fidler@derekfidler
Roles section restructured around the four canonical examples; Discovery role corrected to map to blurple, not purple.
- DocumentedDerek Fidler@derekfidler
Anatomy + roles redesigned: compact saturated grid, light/dark comparisons, role pills.
- DocumentedDerek Fidler@derekfidler
First documented version. Tokens sourced from the Figma export.
- In progressDerek Fidler@derekfidler
Initial draft — palette extracted, semantic mapping outlined.