---
name: componiq-colors
description: The Componiq Colors system — ten hues and a neutral, each in three chroma bands, all standing on one fixed fourteen-step lightness ladder. Use this whenever you choose a color for an interface: page and card surfaces, borders, dividers, body and secondary text, accents, hover, selected and disabled states, focus rings, scrims, charts, or a dark mode. Use it when defining or editing design tokens, when a contrast ratio has to hold, and when swapping one hue for another. Reach for it even when the user has not named the palette — if the task involves picking a color in interface code, this says which rung to take and why, so you never guess a hex or re-check a ratio by hand.
---

# Componiq Colors

A palette built for the parts of an interface that are not accents.

Most palettes are generated at the edge of what a screen can show. That is the
right choice for one button and the wrong one for everything around it —
surfaces, cards, borders, dividers, secondary text, disabled states. This set
supplies those, and it supplies them in a way that makes the choice mechanical
instead of a matter of taste.

## What is in the set

**Ten hues** — Indigo, Purple, Red, Ocher, Amber, Yellow, Green, Emerald, Cyan,
Blue — plus a **Neutral**.

**Three chroma bands per hue** — vivid, soft, muted.

**Fourteen rungs per band**, numbered 1 (lightest) to 14 (darkest). The neutral
carries two extra bookends: rung **0** is the page itself and rung **15** is
absolute ink.

Token names follow the band:

```
--indigo-7          vivid
--indigo-soft-7     soft
--indigo-muted-7    muted
--indigo-alpha-7    the vivid band as opacity
--neutral-7         no hue at all
--neutral-0         pure page   (white in light mode, black in dark)
--neutral-15        pure ink    (black in light mode, white in dark)
```

## The one rule that makes the set work

Every hue and every band stands on the **same lightness ladder**:

```
rung  1   2   3   4   5   6   7   8   9  10  11  12  13  14
L%   98  96  94  92  89  80  70  65  52  44  38  34  28  18
```

`green-9` and `ocher-9` carry identical lightness. They behave identically
against text, so one can take the other's place in a component without
re-checking anything. This is measured, not asserted: across all ten hues at one
rung, contrast against white varies by ±0.12 in the muted band, ±0.35 in soft,
±0.76 in vivid.

**So choose in this order, and the check is already done:**

1. **Rung** — from the job. What does this have to contrast against?
2. **Band** — from how loud it should be.
3. **Hue** — from what it means.

Most palettes make you do this backwards: pick a colour you like, then measure,
then adjust. Here the measuring is finished before you start.

## What each rung is for

Contrast is measured against pure white and pure black. Ranges span all ten
hues and all three bands.

| Rung | L | on white | on black | Its job |
|---|---|---|---|---|
| 1 | 98% | 1.1 | 20 | the page, the lightest surface |
| 2 | 96% | 1.1 | 19 | a raised surface — card, panel, popover |
| 3 | 94% | 1.2 | 18 | a sunken surface — well, inset, track |
| 4 | 92% | 1.3 | 17 | hairline on a light surface |
| 5 | 89% | 1.4 | 15 | border, divider |
| 6 | 80% | 1.9 | 11 | strong border, disabled fill, chart gridline |
| 7 | 70% | 2.7 | 8 | graphic on a dark ground; **not text on a light one** |
| 8 | 65% | 3.2 | 6.4 | the non-text contrast boundary — see the warning below |
| 9 | 52% | 5.5 | 3.8 | the first rung that carries text on white |
| 10 | 44% | 7.8 | 2.7 | comfortable body text on white |
| 11 | 38% | 10 | 2.1 | body text |
| 12 | 34% | 11.8 | 1.8 | emphasised text |
| 13 | 28% | 14.6 | 1.4 | headings |
| 14 | 18% | 18.8 | 1.1 | the darkest ink short of black |

### The three lines that matter

**Body text on a light page starts at rung 9.** Rung 9 clears AA (4.5:1), rung
10 clears AAA on white. Nothing lighter than 9 is body text on a light ground,
however much it looks readable on your monitor.

**Non-text contrast — borders you must see, icons, focus rings — needs 3:1, and
rung 8 sits on that line.** In the soft and muted bands it clears it (3.1–3.5).
In the **vivid** band it runs 3.0–3.8 depending on the hue, so some hues fail.
When a vivid rung has to carry non-text contrast, take **rung 9**, not 8.

**Rung 7 is not a text rung on a light ground.** At 2.5–3.1 it fails even the
large-text threshold. It is for graphics, and for text on a dark ground.

## Text on a surface, not on pure white

The table above is measured against pure white and pure black. Real text sits on
a rung 1–3 surface, which costs a little. The numbers, for the neutral:

| Text rung | on rung 1 | on rung 2 | on rung 3 |
|---|---|---|---|
| 9 | 5.2 AA | 4.9 AA | 4.6 AA |
| 10 | 7.3 AAA | 6.9 AA | 6.5 AA |
| 11 | 9.5 AAA | 8.9 AAA | 8.4 AAA |
| 12 | 11.1 AAA | 10.5 AAA | 9.9 AAA |

Read it this way: **rung 9 holds AA on any surface**, and **rung 11 is the first
that holds AAA on any surface**. Rung 10 is AAA on the page but drops to AA on a
sunken panel — fine unless AAA is a requirement, in which case start at 11.

## Choosing the band

**Vivid** is for accents, and only for accents. The primary button, the active
tab, the selected row, a chart series, a badge that means something. It is the
thing the eye should reach first, and it stops working the moment there are
three of them on a screen.

**Soft** is for surfaces that should read as coloured and still read as
surfaces: a tinted panel, a selected row's background, an info callout. Half the
chroma available at that rung.

**Muted** is the quiet nine tenths. Borders, dividers, secondary text, disabled
states, chart gridlines, any surface that carries a hue only so it does not look
grey beside the accent. Eighteen percent of the chroma available — still
recognisably the hue, never a second accent.

**Neutral** is the same ladder with no hue at all.

The mistake this set exists to prevent is reaching for vivid because it is the
colour you were handed. Most of an interface is not an accent.

## Dark mode

The dark ladder is the light one mirrored **by distance, not by value**:

```
light  98  96  94  92  89  80  70  65  52  44  38  34  28  18
dark   12  14  16  18  21  30  40  45  58  66  72  76  82  92
```

Inverting instead (100 minus each) would collapse the set: at the dark end a
six-point window holds three distinguishable greys where the light end has
twenty-one, so 2, 4 and 6 would all round to black. Mirroring the *gaps* keeps
step 1 to 4 as fine in dark as in light.

The consequence is the useful part: **a rung means the same thing in both
modes.** Rung 1 is always the page. Rung 5 is always a border. Rung 13 is always
a heading. Only the lightness behind the number flips.

So **define tokens by rung and swap the ramp set.** Never remap rung numbers per
mode, and never invert them (`14 − i`).

```css
:root                     { --surface-raised: var(--neutral-2); }  /* 96% */
:root[data-theme="dark"]  { --surface-raised: var(--neutral-2); }  /* 14% */
```

One difference worth knowing: the thresholds shift by one rung.

| | first AA | first AAA |
|---|---|---|
| light | rung 9 | rung 10 on the page, 11 on a panel |
| dark | rung 9 | rung 11 |

In dark mode rung 10 reaches only 6.5:1 on rung 1. If a design claims AAA in
both modes, **rung 11 is the honest floor.**

## Alpha ramps

`--indigo-alpha-7` composites to exactly `--indigo-7` over the page background
of its mode. That makes it a drop-in for the solid wherever you do not control
the ground: a scrim, a hover overlay, a selection crossing an image, a border
running over a photograph.

Two neutral overlays deliberately do not follow the mode — `--black-alpha-N`
(solved over white) and `--white-alpha-N` (solved over black). A white 18% over
a photograph is the same token whether the page around it is light or dark.

Reach for an alpha ramp instead of `opacity` on a solid: `opacity` also fades
whatever sits on top of the element.

## Worked example

A card with a primary action and a disabled twin, in tokens only:

```css
.card {
  background: var(--neutral-2);          /* raised surface */
  border: 1px solid var(--neutral-5);    /* border rung */
  color: var(--neutral-12);              /* body text, 10.5:1 on this surface */
}
.card__meta      { color: var(--neutral-9); }      /* secondary, still AA */
.card__divider   { border-top: 1px solid var(--neutral-5); }

.card__action    { background: var(--indigo-9); color: var(--neutral-0); }
.card__action:hover     { background: var(--indigo-10); }
.card__action:focus-visible { outline: 2px solid var(--indigo-9); }  /* 9, not 8 */
.card__action[disabled] { background: var(--indigo-muted-6); color: var(--neutral-7); }

.card--selected  { background: var(--indigo-soft-2); }
.card__scrim     { background: var(--black-alpha-9); }
```

Every choice above is a rung picked from the job: 2 and 5 because they are the
surface and border rungs, 12 and 9 because they are the text rungs, 9 rather
than 8 for the focus ring because 8 does not reliably clear 3:1 in the vivid
band, muted 6 for the disabled fill because a disabled control is the one place
a hue should be present but silent.

## Things that break the set

**Do not hand-pick a hex.** Every value here is a solved point on a ladder. A
neighbouring colour picked by eye has no rung, and the swap guarantee — that any
hue can replace any other at the same rung — dies with it.

**Do not split the difference between rungs.** If 9 is too dark and 8 too light,
the answer is a different *band*, not a value in between.

**Do not use vivid for surfaces.** That is what soft and muted are for.

**Do not remap rung numbers between modes**, and do not invert them.

**Do not derive a colour with `opacity`** when an alpha ramp exists.

**Do not re-check contrast by eye.** The table above is measured. If you are
near a threshold, read the number; if a number is missing, compute it — but do
not squint at it.

## When the palette is not installed

If the project has no Componiq tokens yet, say so before inventing hexes. The
tokens are generated by Componiq Colors and exported as CSS custom properties,
Tailwind theme, design tokens, Figma variables or SVG. Ask which export the
project uses, or look for `--neutral-1` and `--indigo-7` in the stylesheet — if
those exist, the set is installed and every rule above applies.
