Home · Guides · Fix Failing Contrast Without Changing Hue
Fix a Failing Colour Without Changing Your Brand Hue
"Make it darker until it passes" is how brand colours die. The repair method that keeps the brand intact: change lightness only, in OKLCH, hold the hue, re-measure the actual hex, and then re-grade everything the colour touches.
The worked example: #3B82F6 on white measures 3.677901 — a fail for body text. A lightness-only walk along its own hue reaches #2B72E5 at 4.524816: a pass, with the hue angle unchanged. Same blue, measurably legal, visibly the same colour family.
The method, step by step
- Measure the pair as it stands — unrounded ratio, against the real ground. (3.677901 against white: fail at AA normal, pass at AA large. The target matters before the walk starts.)
- Convert the failing colour to OKLCH and freeze two of its three coordinates: hue, and chroma unless the sRGB gamut forces a reduction (in which case the tool must say so).
- Walk lightness away from the ground — darker on a light ground, lighter on a dark one — in small steps. At each step, convert back to an actual hex colour and measure the hex, not the OKLCH target: rounding to hex can move a ratio across the line, and only the hex ships.
- Stop at the first step that passes. Not a step darker for luck: the smallest sufficient change is the most faithful one, and the step before it must still fail — that is what "smallest" means, and the matrix repair shows its working the same way.
- Re-grade the whole palette. The repaired colour's other pairings changed too. If one of them broke, that is shown immediately, not discovered in the next audit.
Why the walk must be in OKLCH
Lightness in HSL is a formula about max and min channels, not about vision: at the same HSL lightness, a yellow and a blue differ by multiples in measured contrast, so "darken by 10%" is a different-sized change for every hue — and the hue itself shifts as the channels clamp. OKLCH was designed so its lightness axis tracks perceived lightness across hues, which makes a 0.005 step a consistently small, predictable move — the property a smallest-sufficient-change walk depends on. It is also the space the generator builds scales in, so repair and generation can never disagree about what a step means.
The honest limits
Lightness cannot fix everything. Against a mid-luminance ground, a 7:1 target may be unreachable in both directions — the arithmetic, not the tool, says no — and a hue held at very high chroma may clip against the sRGB gamut as it lightens, forcing a chroma reduction that must be disclosed. A repair feature that never says "this cannot be done with the hue held" is not repairing; it is substituting. When the ground is the problem, move the ground — as its own decision, with its own re-grade.
Repair questions
Why not just darken the colour in HSL until it passes?
Because HSL lightness is not perceptual — equal HSL steps produce wildly unequal ratio steps, so the usual workflow overshoots (a colour far darker than needed) or lands on a hex whose hue has visibly wandered. OKLCH lightness tracks perceived lightness, so the walk is fine-grained and the hue angle is a separate, held coordinate. The smallest sufficient change is the goal: a repair you cannot see from across the room, that an auditor measures as a pass.
When can lightness alone not fix a pair?
When the ground sits in the middle: against a mid-luminance background, neither the lightest nor the darkest version of a hue may reach a high target — 7:1 (AAA) against a mid-grey is arithmetically impossible for many pairs. The honest tool behaviour is to say so and offer the ground-side move as a separate, explicit action. A repair that silently edits both colours, or quietly rotates the hue to make the number work, has stopped repairing your colour and started replacing it.
After repairing one pair, why do I need to re-check the others?
Because the repaired colour has other jobs. The brand blue you darkened for body text is also the focus ring on a tinted surface and the chart series next to a sibling colour. A local fix with global side-effects is the normal case in a palette — which is why the workflow here re-grades the whole matrix after every repair and shows both changes, instead of reporting the one you asked about.