Skip to content
Start
Live · open it · 04 of 07

Umber

Palette generation with the contrast maths built into the generator.

Umber
Umber in use
By the count

OKLCH

Generation space, not HSL

7:1

Body text contrast floor, enforced in the generator

50

Regeneration rounds locks were verified over

6

Schemes, from analogous to tetradic

01 · The problem

Why this exists at all.

Palette tools produce a row of swatches and leave the hard part to you. The hard part is not choosing five colours you like. It is discovering, three days into a build, that the accent you picked cannot legally carry text, that two of the five are indistinguishable at a glance, and that the whole set collapses in dark mode.

So the output is a list of hexes and the work of turning that into an interface remains entirely undone.

02 · The approach

The decisions, and what they rule out.

01

Generate in OKLCH, because HSL lightness is a lie

HSL lightness is not perceptual. Pure blue and pure yellow are both 'lightness 50%' in HSL and are nowhere near each other to a human eye. Any generator built on HSL produces palettes whose members are supposedly balanced and visibly are not. Generating in a perceptual space means the numbers describing a palette match what a person sees.

02

Contrast clamps run inside generation, not as a warning afterwards

Every derived tone is clamped as it is produced: body text at or above 7:1, secondary at or above 4.5:1. The generator cannot emit a palette that fails, so there is no report to read and no failing state to correct. The constraint is upstream of the output rather than a validator bolted to the end.

03

Show the colours used, not the colours

The preview renders real interface, buttons, chips, cards, menus, inputs, through a role engine that assigns the palette the way an interface actually would. A colour card tells you nothing about whether a palette works; a button, a disabled state and a focus ring tell you immediately.

04

The application wears the palette you are building

One surface colour, one accent, every minor tone derived under the clamps: applied to the tool's own chrome. If a palette is unusable you find out because the thing you are using becomes unusable, which is a considerably more honest test than looking at swatches.

03 · What testing found

The things thinking about it did not catch.

Every item below is a real defect, found by measurement, with a commit behind it. They are here because the failures are more informative than the features.

01

The colour extractor had to be written, not imported

Off-the-shelf extraction returns colours but not the geometry. No way to say where in the image each colour came from. A median cut was written instead, so every extracted colour can point at the pixels it came from. A brand colour that cannot be traced back to the artwork is an assertion, not a measurement.

02

Locks were verified over fifty regeneration rounds

A locked swatch surviving one regeneration proves nothing; drift shows up over repetition. Fifty rounds, checked. Inserting a colour interpolates rather than randomising, so adding a swatch does not silently rearrange the ones you had already decided on.

03

The role vocabulary is where the bugs live

Two role tokens differing by one word, accent-as-text versus text-on-accent, were transposed in a downstream consumer, and the result was invisible labels in dark mode. The palette was correct; the assignment was not. Colour systems fail at the mapping far more often than at the maths.

Built with
  • TypeScript
  • Vite
  • OKLCH
  • Canvas
Still open

Nothing outstanding. It does what it was built to do, and the known limits are described above rather than parked here.

Open it

Free, no account, running right now.

umber-colour.vercel.app