Building a colour palette from an image (and checking its contrast)
How dominant-colour extraction works, why the swatches are not the ones you expected, and how to read the WCAG contrast figure before setting type in one.
By Wajahat Rasoul ·
How dominant-colour extraction works
A photograph does not contain five colours; it contains tens of thousands. Extraction is a clustering problem: group all those pixels into a small number of buckets, then report one representative colour per bucket.
The palette extractor uses Color Thief, which builds a colour histogram and splits it repeatedly along its widest axis — median cut — until it has as many groups as you asked for. Each group's average becomes a swatch.
Two consequences follow directly from that method, and they explain most surprises.
Why the swatches are not what you expected
The most striking colour is often missing
Median cut is driven by how many pixels hold a colour, not by how much attention it attracts. A red jacket occupying 3% of the frame loses to a grey sky occupying 40%, even though the jacket is what you remember. Crop to the region you care about first — the cropper is the fastest way — and extract from that.
Two swatches look almost identical
That happens when an image is dominated by one hue in several brightnesses: a sunset, a forest, a studio shot on a coloured backdrop. The algorithm is faithfully reporting that the image really is mostly that colour. Asking for fewer swatches usually produces a more useful spread.
HEX, RGB and HSL — three ways to say one colour
| Notation | Example | Best for |
|---|---|---|
| HEX | #2E5AAC | Pasting into CSS, design tools and brand documents. |
| RGB | rgb(46, 90, 172) | Anywhere you need the channels separately, or an alpha value. |
| HSL | hsl(219, 58%, 43%) | Adjusting a colour by hand — hue, saturation and lightness move independently. |
HSL is the one worth learning. Building a tint or shade in HEX means guessing at six characters; in HSL you keep the hue and saturation and move the lightness, which is how consistent scales get made.
Reading the contrast figure
Next to each swatch is a WCAG contrast ratio — the relationship between the relative luminance of two colours, running from 1:1 (identical) to 21:1 (black on white).
| Ratio | Meets | Use |
|---|---|---|
| 4.5:1 | AA, normal text | The everyday minimum for body copy. |
| 3:1 | AA, large text | From roughly 24px, or 19px bold. Also the minimum for UI borders and icons. |
| 7:1 | AAA, normal text | Stricter target; worth aiming at for long reading. |
Two things it is easy to get wrong. First, contrast is a property of a pair of colours — a swatch is not “accessible” on its own, only against the background you put it on. Second, hue does not enter into it: two colours can be wildly different and still fail, because they are equally bright.
Colour alone is never the message
Around one in twelve men has some form of colour vision deficiency. If red versus green is the only thing distinguishing two states, some readers see one state. Add a label, an icon or a pattern alongside it.
Turning swatches into a usable palette
- Extract from an image that already has the mood you want — a reference photograph, a frame from a film, a piece of packaging.
- Pick one dominant and one accent. Most palettes fail by having three colours all competing to be the loudest.
- Build neutrals from the dominant hue. Take its HSL, drop the saturation to single figures, and vary the lightness. Greys tinted towards the palette look deliberate; pure grey often looks flat beside a warm image.
- Check every text pairing you will actually use against the 4.5:1 line, and adjust lightness — not hue — until it passes.
- Export and keep it. The extractor writes JSON for handing to code, and a rendered PNG for pasting into a document or a brief.
One caveat if any of this is heading for print: these are screen colours, and saturated screen colours are exactly the ones ink struggles to reproduce. RGB vs CMYK covers what survives the trip.