I was rebuilding my site and wanted to keep the brand colours. Obvious first move: open the stylesheet and read the variables.
That was the wrong move. The old site declared about seventy colour variables. Nearly all of them were lying.
Declared is not rendered
WordPress with a page builder and a few plugins accumulates colour definitions the way a coat pocket accumulates receipts. Mine had:
--wc-primary: #720eec— WooCommerce's purple. Nothing on the site is purple.- A full
--tutor-color-*set from an LMS plugin I was not using. - Four Elementor "global" colours:
#6EC1E4,#54595F,#7A7A7A,#61CE70.
That last group is the interesting trap. They look exactly like a brand palette — primary, secondary, text, accent, named as such. They are Elementor's factory defaults, unchanged. Only one of the four rendered anywhere, on a single stray widget.
If I had read the CSS and trusted it, I would have rebuilt my site in someone else's default blue.
Ask the page, not the stylesheet
So I asked what was actually painted, and weighted it by how much of the screen it covered:
const area = {};
for (const el of document.querySelectorAll("body *")) {
const r = el.getBoundingClientRect();
if (r.width < 2 || r.height < 2) continue;
const cs = getComputedStyle(el);
if (cs.visibility === "hidden" || +cs.opacity === 0) continue;
const bg = cs.backgroundColor;
if (!bg || bg === "rgba(0, 0, 0, 0)") continue;
area[bg] = (area[bg] || 0) + r.width * r.height;
}
console.table(
Object.entries(area).sort((a, b) => b[1] - a[1]).slice(0, 10)
);Pixel area, not frequency. A colour used on forty tiny icons matters less than one covering two full-bleed sections, and counting elements gets that backwards.
The answer came back in four lines:
| Colour | Where | Painted area |
|---|---|---|
#516AF6 indigo | Full-bleed sections | 1,095,949 px² |
#000000 | Bands, text highlights | 682,505 px² |
#FFFFFF | Body | 96,070 px² |
#62FFD0 mint | Every button | 6,867 px² |
Mint is last by area and it is the most important colour on the site. It is on every button — the only thing anyone is supposed to click. Area told me what dominates; where a colour lands told me what it means. You need both.
Then the part I did not want to hear
Mint is gorgeous. I wanted to use it everywhere. So I checked it:
1.25:1Mint on whiteWCAG AA wants 4.5:1 for body text and 3:1 for large text. Mint against white is 1.25:1. Not marginal — barely visible. Mint can never be text on a light background, at any size, ever.
What it is good at is being a background:
| Combination | Ratio | |
|---|---|---|
| Ink on mint | 16.7:1 | Excellent |
| Ink on indigo | 4.74:1 | Passes AA |
| Indigo on white | 4.43:1 | Large text only |
| White on indigo | 4.43:1 | Fails for body text |
| Mint on white | 1.25:1 | Never |
Note the fourth row. The old site put white text on those indigo sections. It had been quietly failing AA the whole time, and nobody had checked — including me, and it was my site.
So the rule became: mint is a fill, never text on a light surface. Buttons get mint with black text, which at 16.7:1 is one of the most readable pairings on the whole site. The colour I loved most turned out to be the colour with the narrowest legitimate use, and constraining it made it stronger.
The audit that found the bugs I made next
Having been burned once by eyeballing, I wrote the check as a script — walk every visible text node, resolve its real background through its ancestors, compare against the threshold for its rendered size and weight:
const need = (px >= 24 || (bold && px >= 18.66)) ? 3 : 4.5;
if (ratio < need) fails.push({ text, ratio, need });The ancestor walk is the part that matters. An element usually has
background-color: rgba(0,0,0,0) and inherits its real backdrop from three
divs up. Compare against the element's own background and you will check almost
everything against transparent and conclude the page is perfect.
First run: eleven failures. None of them were the new palette. They were mine, from the previous build:
- A light grey at 1.48:1 on white, used for card numbers.
- A dark grey at 3.03:1 on black, in the footer.
I had two greys, one for light backgrounds and one for dark, and I had used each of them on the wrong one. Both looked fine to me on a good monitor at full brightness. Both were unreadable in daylight.
What I actually took away
I went in to extract colours and came out with a process:
- Measure what renders, not what is declared. Config files describe intent. The rendered page is the only record of what shipped.
- Weight by area, then check placement. Area finds the dominant colour; placement finds the important one. They are rarely the same.
- Check contrast before you fall in love. It is much easier to give a colour a narrow job at the start than to pull it back out of forty components later.
- Turn the check into a script. The bugs I found were not in the palette I was scrutinising. They were in the code I had already convinced myself was done.