Layer 01 · DesignDNA

Palette engineering: one seed colour, a whole system

A palette is not a row of swatches. It is a set of roles — surface, ink, accent, state, border — each with enough steps to survive hover, focus, disabled, light ground and a chart legend. This guide covers how the generator builds that set and how to overrule it.

Raw colours versus role tokens

The first decision is naming. A token called --orange-500 describes a pigment; a token called --color-action describes a job. Generated systems emit both layers: a primitive ramp (nine to eleven steps per hue) and a semantic layer that points at it. Components only ever reference the semantic layer. When a brand shifts from orange to red, you repoint six semantic tokens instead of hunting through two hundred component rules.

The semantic set DesignDNA emits is deliberately small, because every extra role is a decision someone has to make again later:

  • Surface — page ground, raised panel, sunken well, overlay scrim.
  • Ink — primary text, secondary text, inverse text, disabled text.
  • Accent — action, action-hover, action-pressed, action-quiet.
  • State — success, warning, danger, info, each with a tinted surface variant.
  • Line — hairline, divider, focus ring.

Building the ramp

Three common methods, each with real trade-offs:

Ramp generation methods compared
MethodStrengthWeakness
Hand-picked swatchesHighest taste ceiling; art direction stays intactSlow, inconsistent across hues, hard to extend
HSL lightness stepsTrivial to compute in plain CSSPerceptual lightness drifts badly — yellows read far brighter than reds at the same L value
Perceptual space (OKLCH / LCH)Even perceived steps across hues; predictable contrastNeeds a fallback for very old browsers; raw values are less readable in review

The generator defaults to a perceptual ramp with hex fallbacks emitted alongside. Lightness steps are spaced so that step 100 sits near the page surface and step 900 is safe for body text on that surface. Chroma is tapered at both ends: near-white and near-black steps lose saturation, which stops light tints looking like a highlighter and dark tints looking muddy.

Contrast is a constraint, not a review note

The published WCAG 2.1 thresholds are 4.5:1 for normal body text, 3:1 for large text (roughly 24px regular or 18.66px bold and above) and 3:1 for the visual boundary of interactive components and meaningful graphics. The generator checks every emitted pair before naming the direction, not after. Three pairs get checked that teams often miss:

  1. Hover and pressed states, not only the resting state. A button that passes at rest and fails on hover still fails.
  2. Placeholder text and helper text, which tend to be set two steps too light.
  3. The focus ring against both the component fill and the page surface behind it, since a ring can straddle the edge. A bright orange fill, for example, needs a near-black ring rather than a red one.

Mood mapping

Mood is the input that most affects the palette, so it is mapped explicitly rather than left to intuition.

How stated mood biases palette construction
MoodHue behaviourContrast & chroma
Premium / restrainedNarrow hue range, one accent onlyLow chroma surfaces, high ink contrast
Organic / warmWarm neutrals, orange and red accents, analogous spreadMid chroma, softened extremes
Futuristic / technicalNeutral ground with one high-chroma signal hueFlat surfaces, luminous accent edges
Friendly / approachableTwo accents at a wide hue distanceLight surfaces, mid ink, generous tint steps
Editorial / seriousNear-neutral ground, single ink accentHigh contrast, chroma reserved for links

Light grounds need different arithmetic

A light theme is not an inverted dark theme. Pure black text on pure white produces glare for many readers, so the generator keeps both ends off the extremes — a warm off-white surface around 97–99% lightness and a deep warm ink around 12%. Elevation on a light ground is expressed mainly through a warm tinted shadow and a hairline border, because a neutral grey shadow on a warm surface reads as dirty. Saturated accents also need their chroma held back slightly on white, since an orange that sings on dark can vibrate on paper-white.

Pros and cons of generating colour at all

Pros. Consistency across hues is guaranteed. Contrast failures are caught before code. A new state colour can be added in seconds with the same maths, and the whole set can be regenerated from a new seed without re-deriving every tint by hand.

Cons. Algorithmic ramps can feel tidy but characterless. Brand colours rarely sit exactly on a computed step, so the seed usually needs pinning to a specific position in the ramp. And some hues — mid-greens and yellows especially — need manual chroma nudges no formula gets right first time. Treat the output as a first draft with a guaranteed floor, not a finished art direction.

Export format

Systems ship as CSS custom properties on :root, mirrored to a flat JSON file so the same values can drive documentation, design tooling or a native build. Primitives are prefixed --c-, semantics --color-. Keeping the two prefixes distinct makes a code review question — "why is a component reading a primitive?" — visible at a glance.

Next layer

Colour sets the mood; type sets the voice. Continue with typography and scales, or see how palette roles get applied to controls in components and cards.