Auditing a palette you inherited

Forty-three hex codes across nine files, three of which are the same color. A method for working out what is actually there.

↑ Click any of these to repaint the entire site with it.

You join a team. The design system is a folder of components and a stylesheet with forty-three hex codes in it. Some are tokens, some are inline, three of them are #f5f5f5, #f4f4f5 and #f5f5f4, and nobody can tell you which is correct.

Before you can improve it you have to know what it is. Here is an order of operations that works.

1. Extract everything#

Every hex, rgb, hsl and named color in the codebase, with a count of how many times each appears.

grep -rhoE '#[0-9a-fA-F]{3,8}\b' src/ | tr 'A-F' 'a-f' | sort | uniq -c | sort -rn

The counts matter as much as the values. A color used two hundred times is load-bearing. A color used once is either a mistake or an undocumented special case, and both are worth knowing about.

2. Collapse the near-duplicates#

Sort by perceptual distance and merge anything under ΔE 3. Those are colors no one can tell apart, and each one is a decision somebody will have to make again later.

Expect to lose 30–40% of the list at this step. It is the single highest-value hour in the whole exercise.

3. Sort by lightness, not by hue#

This is the diagnostic that tells you whether there is a system underneath.

Lay every remaining color out in order of OKLCH lightness. A palette with a system looks like a ramp — evenly spaced steps with obvious groupings. A palette without one looks like a pile: three colors clustered at 62%, nothing between 40% and 55%, and two outliers at the ends.

The gaps are the interesting part. A hole in the middle of the range means that when somebody needed a mid-tone they invented one, and it is somewhere in the codebase not matching anything.

4. Check every pair#

Build the contrast matrix. Every color against every other color.

You are not looking at individual cells — you are looking at the shape of the passing region. A healthy palette has a clear band of usable combinations. A palette where almost everything is amber has too many colors at the same lightness, which is the same finding as step three arriving by a different route.

5. Simulate color vision#

Run the semantic colors through protanopia and deuteranopia. Success and danger merging is the near-universal finding, and the fix is an icon rather than a different green.

Also check any categorical set — chart series, tag colors, avatar backgrounds.

6. Find the orphans#

Colors that appear once, colors that appear only in one component, colors that do not fit any group. Each is a small decision somebody made under time pressure. Most can be mapped onto an existing token; the rest are telling you about a genuine need the system does not cover.

What to do with the findings#

Do not rebuild. Map.

Write down the palette you want — a dozen semantic tokens over a generated ramp — and then write the mapping from every existing color to its replacement. Ship the mapping as a deprecation table.

/* deprecated: use --color-surface */
--gray-100: var(--color-surface);

That way existing code keeps working, new code uses the new names, and you can delete the old ones when the count reaches zero. A rebuild that requires touching every component at once will not get merged.

The one number to report#

If you have to summarise the audit in a single figure, use the lightness spread: the distance between the lightest and darkest colors in OKLCH.

Under 0.4 and the palette has no hierarchy, whatever else is true of it. Over 0.7 and there is room to build one. It is a cruder measure than the contrast matrix and it is the one that non-specialists immediately understand.