OKLCH Studio · Guides
How to build an accessible colour palette (with OKLCH)
Updated 2026-10-17
An accessible palette is not a palette made only of dark colours. It is a palette in which every colour you intend to use as text, as an icon or as a boundary has a known, tested relationship with the surface it sits on. If you build that relationship into the structure of the palette, you stop checking colours one at a time and start choosing from steps you already know are safe.
This guide sets out a practical method using OKLCH tonal scales, then tests a real scale, Tailwind CSS v4's blue, against white and black so you can see where the safe steps are.
Start with roles, not swatches
Most palette trouble comes from picking colours before deciding what they are for. Start with the jobs the colours do: page background, raised surface, primary text, secondary text, border, accent, link, and the status colours for error, warning, success and info. For each job, write down what it sits on and what it must reach: 4.5:1 for text, 3:1 for icons, borders and focus rings.
The pairs that matter are few. Primary text on every background it can appear on, secondary text on the same, accent on the page, and white or black text on every filled button. Once you have that list, the palette's task is to supply steps that satisfy it.
Build tonal scales in OKLCH
A tonal scale is a series of steps for one hue, running from nearly white to nearly black. Tailwind's 50 to 950 scale is the best-known example, and the Tailwind v4 colours page lists every stop with its OKLCH value. Build your own the same way: fix the hue, choose a target lightness for each step, and set chroma to follow a gentle curve, reducing it at the extremes where sRGB cannot hold much colour.
OKLCH helps because lightness means the same thing in every hue. If step 600 is at lightness 0.55 in blue, you can put step 600 of green and red near the same lightness and expect the same sort of contrast on white. Your scales then behave in parallel, which is what lets you say "use 700 for text on white" and be right for every hue. The palette scale tool on this site takes one brand colour and produces eleven OKLCH steps with light and dark roles, and exports Tailwind v4 theme variables.
A worked audit: Tailwind v4 blue
The table audits all eleven steps of Tailwind v4's blue as text on pure white and on pure black. Hex, lightness and ratios are computed by this site's colour code, and the AA columns use the 4.5:1 normal-text threshold.
| Step | Hex | OKLCH L | On white | AA on white | On black | AA on black |
|---|---|---|---|---|---|---|
| blue-50 | #EFF6FF | 0.970 | 1.08:1 | Fail | 19.29:1 | Pass |
| blue-100 | #DBEAFE | 0.932 | 1.22:1 | Fail | 17.21:1 | Pass |
| blue-200 | #BEDBFF | 0.882 | 1.42:1 | Fail | 14.76:1 | Pass |
| blue-300 | #8EC5FF | 0.809 | 1.81:1 | Fail | 11.58:1 | Pass |
| blue-400 | #51A2FF | 0.707 | 2.63:1 | Fail | 7.96:1 | Pass |
| blue-500 | #2B7FFF | 0.623 | 3.76:1 | Fail | 5.58:1 | Pass |
| blue-600 | #155DFC | 0.546 | 5.24:1 | Pass | 4.00:1 | Fail |
| blue-700 | #1447E6 | 0.488 | 6.83:1 | Pass | 3.07:1 | Fail |
| blue-800 | #193CB8 | 0.424 | 8.82:1 | Pass | 2.37:1 | Fail |
| blue-900 | #1C398E | 0.379 | 10.36:1 | Pass | 2.02:1 | Fail |
| blue-950 | #162456 | 0.282 | 14.78:1 | Pass | 1.42:1 | Fail |
On white, the first step that passes AA is blue-600, at OKLCH lightness 0.546. 5 of 11 steps pass on white. On black, steps up to blue-500 pass, at lightness 0.623, and 6 of 11 steps pass. No step passes on both backgrounds, which is why a single mid-tone brand colour rarely works as text in both a light and a dark theme.
Across Tailwind's whole palette, 156 of 286 stops fail AA as text on white. The Tailwind AA research report has the full tally, and each stop has its own page, for example blue-600, with contrast pairs and a suggested fix. The shadcn/ui themes use the same OKLCH approach and include contrast audits of their own tokens.
Pick pairs by step distance
Because the scale is ordered by lightness, accessibility becomes a rule about distance. In the blue scale above, a step in the lower half of the lightness range, from 600 onward, can be text on white, while the lighter steps are for backgrounds, fills and borders. A reliable approach is to document pairings as step offsets: text uses step 700 or darker on a step 50 background, and large text can use 600. Anyone adding a new hue then follows the same rule without re-measuring each colour, although you should still run the check once to confirm.
- Neutral scales matter most. Body text is mostly neutral, and your greys are the colours that fail quietly, especially secondary text and placeholders.
- Filled buttons need a label colour per fill. White often fails on bright mid-tones such as amber, lime and sky; dark text is the answer there.
- Test the in-between steps you actually use for hover and disabled states, not just the headline colours.
State colours: error, warning, success, info
Status colours need two properties: they must be readable, and they must be distinguishable by more than hue. Pick the text version of each status colour from the same step range you use for body text, and the fill version from the lighter end, with a dark label. Do not use the bright accent as text colour. A red used for an error border at 3:1 might be wrong for error text at 4.5:1.
Pair every colour signal with another one: an icon, a label, or a different shape. A red outline alone tells a red-green colour-blind user nothing. The relevant WCAG rule is success criterion 1.4.1, Use of Color, which says colour must not be the only way to convey information.
Dark mode is a separate palette
Dark mode is not the same palette inverted. The rule that put step 600 on white does not put step 600 on black, as the table shows. Choose new steps for the dark surfaces, usually lighter and slightly less saturated for text, and darker for the surfaces themselves. A good dark surface is a very dark but not pure-black neutral, with elevated surfaces one small lightness step lighter.
Re-run every pair from your list against the dark surfaces, including the status colours. Check large areas of saturated colour as well: a vivid fill on a dark page can be harsh even when the ratio is acceptable, and this is where a quick look at APCA Lc, covered in WCAG vs APCA, can catch weak spots.
Colour blindness and not relying on colour alone
Colour vision deficiencies change which hues are distinguishable, but they do not remove lightness differences. That is another reason to build palettes on lightness: a design in which two states differ in lightness, as well as hue, survives most colour-vision differences. Use lightness difference, labels, icons, patterns or underlines to carry meaning, and treat hue as decoration on top.
The studio's UI preview has simulation modes for protanopia, deuteranopia and tritanopia, so you can look at a palette applied to an interface under each. Simulations are an approximation of real vision and they are not a replacement for testing with people, but they quickly expose pairs that differ only in hue.
A short checklist closes the loop. Roles are written down. Each text colour is a scale step with a measured ratio. Status colours have a non-colour cue. Dark mode has its own steps. Every pair is checked, and the failing ones are repaired with the fix method.
Ready-made accessible palettes
If you would rather start from a finished palette, the accessible palettes collection has 31 of them, each generated with the method above and verified pair by pair. Examples: healthcare, fintech, government, dark mode, forms and status colours and colour-blind-safe chart colours.
FAQ
- What makes a colour palette accessible?
- Every colour used for text, icons or borders has a measured contrast with its background that meets WCAG AA: 4.5:1 for normal text and 3:1 for large text and UI components. Meaning is also never carried by colour alone. Building the palette from lightness-ordered scales makes this systematic.
- Which steps of a tonal scale are safe for text?
- It depends on the scale and the surface. For Tailwind v4 blue, text passes AA on white from blue-600 onward and passes on black up to blue-500. Measure your own scale rather than assuming the same step works for every hue, though a lightness-based scale usually behaves similarly across hues.
- Can one colour work as text in both light and dark themes?
- Rarely. A mid-tone that passes on white usually fails on black, and the reverse. Define separate steps for light and dark mode, and keep the hue and chroma consistent between them so the brand still reads the same.
- How do I design for colour blindness?
- Do not use colour as the only signal. Add icons, labels, patterns or underlines, and rely on lightness differences, which colour-blind users can still see. Use a simulation of protanopia, deuteranopia and tritanopia as a quick check and test with real users where you can.
- How many steps should a scale have?
- Eleven is common because it gives enough room for backgrounds, borders, fills and text. Tailwind uses 50 to 950. More steps give finer control but are harder to document, so choose the number your design system can describe in rules.
Try it
Enter one brand colour and get an eleven-step OKLCH scale with light and dark roles that you can export as Tailwind v4 variables.