Random colour

Uniform over all 16,777,216 sRGB colours — with the luminance, the contrast both ways, and which of black or white text to put on it. Nothing you type here leaves your browser — it is worked out on your own device, so there is nothing to send and nothing to log.

This page will not fall back to Math.random(). Everything here is drawn from crypto.getRandomValues(), which the browser backs with the operating system's cryptographic source. If that is unavailable the page says so and produces nothing — because output from a weak source looks exactly like output from a strong one, and that is the whole problem.

Every colour takes black text or white text. None takes neither.

Contrast against white is 1.05 ÷ (Y + 0.05); against black it is (Y + 0.05) ÷ 0.05, where Y is the WCAG relative luminance. Set them equal and you get the point where you should switch:

(Y + 0.05)² = 0.0525, so Y = 0.179129

At exactly that luminance both black and white give 4.5826:1 — and that is the worst case. Every colour in sRGB is better than that one way or the other, so no colour fails the 4.5:1 minimum against both. There is always a readable choice; it is just not always the one that was picked.

And the split is not fifty-fifty

You might expect half of all colours to want white text. Counted over all 16,777,216 sRGB colours — exactly, not sampled — the answer is 35.56%. Nearly two thirds want black.

The reason is that luminance is not an average of the channels. Green carries 71.52% of it, red 21.26% and blue only 7.22%, and the sRGB transfer curve bends the rest. A colour picked uniformly from the cube is brighter than it looks like it ought to be.

Worth knowing if you are sampling the cube for anything: a coarse lattice gets this wrong. Taking every eighth value gives 37.65%, every fourth 36.46%, every second 35.86%. The drift is one-directional, because coarse grids over-weight the dark corner.

Uniform in RGB is not uniform to the eye

Each channel here is drawn uniformly from 0 to 255, so every one of the 16.7 million colours is equally likely. That is uniform in the cube, which is not the same as uniform in appearance. Equal steps in RGB are nothing like equal steps in brightness, and the green axis dominates both.

If you want a palette, this is the wrong tool: it is a dice roll with a contrast figure attached. What it is good for is showing what “random colour” actually means.

Where the randomness comes from

Three bytes from crypto.getRandomValues(), one per channel, which is exactly 24 bits and needs no rejection step because 256 is the whole range of a byte. Not Math.random(), and no fallback.

Where to go next