One job per color

Each color has one job: identity, order, magnitude, polarity or state.

From dataviz (references/color-formula.md and references/palette.md).

Theme

Four data types, four palettes

Pick a dataset, then a palette. Tap cells to read them.

Data
Palette
Your paletteMismatch
Matched to the dataMatch

const JOB = {
  identity:  "categorical", // fixed slots, max 3 in a grid
  magnitude: "sequential",  // one hue, light → dark
  polarity:  "diverging",   // two poles, gray zero
  state:     "status",      // reserved hues + icon
};

Diverging: gray at zero

Change midpoint, poles and steps; watch the near-zero cells.

Midpoint
Poles
Steps
Your diverging scaleBad
Blue ↔ red, gray zeroGood
color-mix(in oklab, var(--pole) k/n, var(--mid))

:root              { --mid: #f0efec; --pos: #2a78d6; --neg: #e34948; }
:root[data-theme="dark"] { --mid: #383835; --pos: #3987e5; --neg: #e66767; }

function diverging(v, max = 40, n = 3) {
  const s = Math.sign(v) * Math.min(n, Math.round((Math.abs(v) / max) * n));
  if (s === 0) return "var(--mid)";
  const pole = s > 0 ? "var(--pos)" : "var(--neg)";
  return `color-mix(in oklab, ${pole} ${(Math.abs(s) / n) * 100}%, var(--mid))`;
}

Rainbow vs one hue

Which marked cell is larger? Answer in both panes.

Rainbow 0 / 0 · Blue ramp 0 / 0

Rainbow rampBad
Blue 100 → 700Good
:root {
  --seq-100: #cde2fb; --seq-150: #b7d3f6; --seq-200: #9ec5f4; --seq-250: #86b6ef;
  --seq-300: #6da7ec; --seq-350: #5598e7; --seq-400: #3987e5; --seq-450: #2a78d6;
  --seq-500: #256abf; --seq-550: #1c5cab; --seq-600: #184f95; --seq-650: #104281;
  --seq-700: #0d366b;
}

Why it matters, pros and cons

Why it matters

A palette whose structure matches the data decodes without a key. A mismatched one decodes wrong, confidently.

Pros

  • A rule a review can check
  • Readers learn the grammar once
  • Dark mode reuses the same ramps

Cons

  • Diverging needs a real zero, else use sequential
  • Grids cap categorical at three hues
  • Status hues need an icon and label