Skip to content
Recursica

Colors

Recursica uses a structured, tokenized color system to manage and apply color in a flexible and scalable way. It provides accessible default palettes while offering designers and developers the freedom to extend and customize them.

Color definitions follow the W3C Design Tokens Community Group format: every color is a named token with a typed value, and semantic roles reference those tokens as aliases rather than repeating raw values. Palettes are authored and validated in Theme Forge, then exported as standard DTCG JSON and CSS custom properties for use in design tools and code. Architecture covers how the files relate; this page is about the color decisions inside them.

Palette Structure

By default, the design system defines three main color scales:

  • Primary Palette (Palette 1): The main brand color used for key interactive elements, focus states, and primary actions. It defaults to a vibrant Cornflower theme.
  • Secondary Palette (Palette 2): A supporting brand color used to create visual interest, secondary highlights, or accent elements.
  • Neutral Palette (Neutral): A grayscale palette ranging from deep black to pure white, used for background surfaces, borders, dividers, and text.

Color Tones

Each color scale is a gradient of 12 tones, sequentially labeled from 000 (lightest) to 1000 (darkest).

  • Default Tone: A specific tone within the scale (typically 500) is designated as the default color for general application.
  • On-Tone (Foreground contrast): For each background tone, an “on-tone” color is calculated to ensure that overlapping text and iconography maintain readable contrast, meeting WCAG AA accessibility standards.
  • Emphasis Opacities: To establish visual hierarchy without creating extra color variables, on-tone text uses opacity values to indicate high-emphasis (fully opaque) or low-emphasis (semi-transparent) text. There are only ever two emphases; a third is a sign a value is being invented rather than referenced.

Fills versus foregrounds

Every semantic role resolves to two different values, and choosing the wrong one is the most common color bug in the system:

  • tone is a fill. It is what you paint a surface, a solid button, or a badge with.
  • color is a foreground. It is the contrast-checked value for text, icons, and links drawn against the corresponding surface.
  • on-tone is the foreground for content sitting on top of that tone’s fill.

An interactive element illustrates it: elements_interactive_tone fills the button, elements_interactive_color paints a text link, and on-tone paints the label inside the filled button. Using tone where color belongs produces text in the button’s fill color, which can be a pale tint that fails AA against the page behind it.

Core colors

Alongside the palettes sits a set of core colors: the semantic roles every theme must define, whatever its palettes look like. There are six.

  • High contrast — the strongest surface the theme can produce (near-black in light mode, near-white in dark). Used for banners and callouts that must cut through everything around them.
  • Low contrast — its opposite, the quietest surface the theme can produce. High and low contrast reference each other’s tone as their own on-tone, so the pair is always legible in both directions.
  • Interactive — the color of things a person can act on: buttons, links, focus rings, selected states.
  • Alert — errors, failures, validation problems, and destructive actions that need correction.
  • Success — completed operations and positive confirmations.
  • Warning — non-blocking cautions and details that deserve a second look.

The three properties every core color carries

Each core color defines the same trio, and picking the wrong one is the most common color bug in the system:

  • tone — the fill. What you paint a surface, a solid button, or a badge with.
  • on-tone — the foreground for content sitting on top of that fill. This is what the label inside a filled button uses.
  • interactive — the actionable color within that context. A link inside a high-contrast banner needs a different value than a link on the page canvas, and this is it.

That trio is why a status banner works without any bespoke styling: the alert tone fills it, alert on-tone paints the text, and alert interactive paints the “Learn more” link inside it, all contrast-checked against each other.

High and low emphasis

Text hierarchy comes from opacity, not extra colors. Each theme defines exactly two emphases, applied to the layer’s text color:

  • High emphasis — fully opaque. Headings, body copy, anything a person is meant to read.
  • Low emphasis — the theme’s reduced opacity. Captions, metadata, supporting text.

There is no third tier. If a value looks like it needs one, it is a sign a color is being invented rather than referenced. Because emphasis is an opacity over the layer’s own text color, it stays correct automatically when the surface beneath it changes.

Interactive, in practice

The interactive role is the one people most often get wrong, because it resolves to different values depending on what you are painting:

  • interactive_tone fills a solid button.
  • interactive_on-tone paints that button’s label.
  • interactive_color paints a text link, and is the contrast-checked foreground of the pair.

A link painted with interactive_tone renders in the button’s fill color, which is often a saturated tint that fails AA as text. When in doubt: if it is text or an icon, it wants color; if it is a shape, it wants tone.

Layered Abstraction

To support styling flexibility without code refactoring, Recursica abstracts color mapping across files, mirroring the DTCG’s distinction between primitive tokens and semantic aliases:

  1. Tokens File: Maps raw hex values to specific color names (e.g., #DD2C56 becomes ruby-red). These are the primitive tokens.
  2. Brand File: Maps token names to roles across the Primary, Secondary, and Status scales (e.g., setting ruby-red as the default tone for the Alert scale). These are aliases that reference primitives, never raw values. This is also where a role’s color changes between light and dark: the same name, a different primitive.
  3. UI-Kit File: Maps component properties to brand roles, so a button’s background names an interactive role rather than a palette tone.

This division allows a team to change what a color is (in the Tokens file) or what a color means (in the Brand file) independently, without changing component definitions or CSS class names. All three are edited in Theme Forge and exported together; see Architecture for the full reference chain.

Color is also depth-aware. The same semantic role resolves to different values depending on which layer an element sits in, so a surface stays distinguishable from the surface beneath it and its foregrounds stay contrast-safe at every depth.