Colour

Fixing a brand colour that fails contrast

The brand hex is fixed, so something else has to move. Here is the order I try the alternatives in, the arithmetic behind darkening a fill, and where each one breaks.

A brand colour that fails contrast is a particular kind of problem, because the one thing you want to change is the one thing you have been told not to touch. The hex is in the brand book. It is on the van, in the app icon, on the folder the sales team hands out. Somebody senior signed it off in a meeting you were not in, and "make it a bit darker" is not a sentence you get to say back.

So take a concrete case. The primary is #2C99E2, a clean mid blue. You build the primary button the way the book says: white label, brand fill. The checker returns 3.11:1. Body text needs 4.5:1. The fill cannot move, so something else has to, and the useful thing to understand is that there is a short list of candidates and each one costs the brand a different amount.

The ratio is a statement about lightness

The number a checker hands you compares relative luminance, not brightness and not saturation. White text on #2C99E2 works out like this, with the sRGB transfer function undone on the way:

channel   raw   ÷ 255     linearised
R          44    0.1726    0.0252
G         153    0.6000    0.3186
B         226    0.8863    0.7605

luminance  = 0.2126 × 0.0252 + 0.7152 × 0.3186 + 0.0722 × 0.7605
           = 0.0054 + 0.2278 + 0.0549
           = 0.2881

white on it = (1.0 + 0.05) ÷ (0.2881 + 0.05)
            = 1.05 ÷ 0.3381
            = 3.11

Two facts in that block do all the work in this piece. The first is the weights: green carries 0.7152 of the luminance and blue carries 0.0722, so a hue's luminance has almost nothing to do with how bright the hue looks. Three colours that a picker would call roughly the same lightness, all near the middle of the scale, come out like this.

0.2881#2C99E2, lightness 53%
0.3278#4CAF50, lightness 49%
0.5941#F5C518, lightness 53%

The yellow carries better than twice the luminance of the blue at the same nominal lightness. That is why a remedy costs a different amount depending on the hue you brought, and why "darken it a bit" is a different job for every brand.

The second fact is the 0.05 in the denominator. The ratio is 1.05 divided by luminance plus 0.05, so as luminance falls the divisor falls towards its floor and each further drop buys more ratio than the one before. Hold onto that, because it explains the thing that surprises everyone about the method below.

Darken the fill until white text passes

This is the remedy I reach for first, and for a button it is nearly always right. Take the hex into HSL, keep H and S exactly where they are, walk L down in steps of five percentage points, convert back to hex, and re-check the ratio at every step. Stop at the first step that clears 4.5. The brand survives because the hue angle never moves. What moves is the value, and a fill four or five points down the scale is a shade nobody outside the brand team can name.

#2C99E2   H 204   S 76%   L 53%   3.11
#1E8DD7   H 204   S 76%   L 48%   3.59
#1A7EC1   H 204   S 76%   L 43%   4.38
#176FAA   H 204   S 76%   L 38%   5.40   first pass

Read the last two lines together. Step two is 4.38, which is not enough. Step three is 5.40, which is more than enough. There is no step that lands on 4.5, so you take the first one past it, and past it is usually well past it. That overshoot is what the 0.05 buys you: the ratio rises faster the darker you go, so the last step of any walk is always the biggest one.

A five-point step is therefore not a fixed quantity of anything, which is the part people get wrong when they try to do this in their head. Near the top of the lightness range a step buys a few tenths of ratio. Near the bottom it can buy more than a point at once. The teal #00A896 sits at L 33% and 2.98:1, and one step takes it to 4.06:1.

#00A896   H 174   S 100%   L 33%   2.98
#008E7F   H 174   S 100%   L 28%   4.06
#007568   H 174   S 100%   L 23%   5.61   first pass

Two steps, where the blue needed three. If the brand colour is dark already the walk is short and cheap; if it is bright you will spend a lot of lightness, and what you get at the far end is not always the colour you started with.

The traps in the walk

The method is mechanical, and it goes wrong in ways the arithmetic hides until you look at the result.

A saturated yellow comes out brown

#F5C518 is not a hypothetical. It is the kind of yellow that lands in a brand book because it looks like sunlight on a swatch, and its luminance is 0.5941, more than twice the blue's. White text on it starts at 1.63:1 and has an enormous distance to fall.

#F5C518   1.63
#E9B90A   1.84
#D1A609   2.29
#B99208   2.93
#A07F07   3.80
#886B06   5.06   first pass

Five steps, and the destination is olive. The hue angle is identical — #F5C518 sits at 47 degrees and #886B06 sits at 46.6 — and that is the trap exactly, because a hue angle is not what anyone sees. What makes a yellow read as yellow is brightness and chroma, and the walk has spent both. Nobody calls #886B06 the yellow one. They call it brown, because there is no such thing as a dark yellow that still looks yellow.

One partial escape is to let the hue drift while the lightness falls, about two degrees per step from 36 towards 22. On the amber #F2A93B that reaches 4.58:1 on the fourth step at #BB5A0C, where a straight walk needs five steps and lands at #A3660B. One step earlier means five percentage points more lightness survives, and more orange is left in it. A mitigation, not a cure.

Blue keeps its name

The blue does the opposite. #176FAA is thirteen HSL points below where it started and still reads as the same blue, a deeper and slightly navy version of the button you drew; there is no register in which that is not blue. #886B06 has no such register, because the word for a dark yellow is brown. The arithmetic did the same thing to both, and the hue angle held to within half a degree either way. What differs is perception, so the brand damage is not a property of the formula at all. It is a property of the hue you brought, and the same method is close to free for one brand and ruinous for another.

The dark panel is a separate calculation

Fixing the light theme tells you nothing about the dark one. #2C99E2 as text on a dark panel, #16181B, measures 5.73:1, so a dark theme built on the original hex costs nothing. The value you just chose to make white text work, #176FAA, measures 3.30:1 against that same panel. The darker fill sinks into the dark surface, and if one token feeds both themes you have traded one failure for another.

The pair you measured is rarely the only pair your colour will be in. White on the fill is 3.11:1 and fails. Near-black #111111 on the same fill is 6.08:1 and passes. The fill against the page is a third pair, the fill against the panel a fourth. Write the light and dark values down separately, check each against the surface it will actually sit on, and do not assume that a hex which passes in the state you were looking at passes in the state you were not.

The remedies, in order of damage

The order below runs from the remedy that changes the fewest things about the brand to the one that changes the most. Darkening moves a value. Flipping the text moves the text. Enlarging moves the type. Framing moves the shape. Moving the colour moves its job.

RemedyBrand damageUse it when
Darken the fill The hue is untouched, and white goes from 3.11:1 on the original to 5.40:1 on #176FAA. What is lost is the exact hex as a surface: the book's swatch and your button no longer match, and they will not match in print either Any solid button, badge, banner or chart label carrying white text. The default answer, and it works for most hues
Flip the text colour None to the fill, which survives exactly, so the swatch still matches. #111111 on it measures 6.08:1 against white's 3.11:1. Dark on brand reads lighter and more decorative, and it inverts the hierarchy if every other control is white on dark High-luminance brands: yellow, lime, cyan, pale amber. On those it beats darkening, which destroys the hue instead
Enlarge and embolden the label Nothing about the colour changes at all. The type buys the 3:1 large-text bar, which starts at 24px or 18.66px bold, and #2C99E2 already clears it at 3.11:1. The control gets bigger and the layout absorbs the difference A primary action in a layout that can carry a heavier button, and only while the label stays short. Never for body copy or links
Frame the button The fill and the text are both untouched, so the cost is honesty: a border or a shadow makes the button look finished while the label is still 3.11:1 for anyone with low vision Only alongside one of the others. On its own it makes the page look solved and leaves it unsolved
Move the colour to an accent Prominence, and that is all. The hex is still on the page as a rule, an underline, a filled icon or a tab indicator, just not behind text, and the loudest surface on the screen stops being the brand When the hex cannot move and no other remedy is honest: saturated yellows, and palettes where the swatch was signed off by someone you cannot reach

Read the damage column as what the brand team will notice, not as how hard the change is to make. The first two rows are a two-line change in code each, and they cost completely different amounts.

Two things in that table are worth pulling out. Remedy one is not always available: for a yellow or a lime it is a way of spending the brand to buy a number, and flipping the text is strictly better, because #111111 on #F5C518 is 11.58:1 and the swatch never moves. And framing is the remedy people reach for when they have run out of time. It fixes the shape and leaves the reading exactly as it was, which is the wrong half of the problem.

Worked examples

Every row below is a real walk with the five-percent step, on a colour that fails against white text. The third column is the first value that clears 4.5, and the fourth is white text measured on it.

Brand colourWhite text on itAfter the walkWhite text on thatSteps
#2C99E2 blue3.11#176FAA5.403
#E4572E red-orange3.68#C5401A5.092
#4CAF50 green2.78#357A385.263
#00A896 teal2.98#0075685.612
#F2A93B amber2.00#A3660B4.715
#F5C518 yellow1.63#886B065.065

The last two rows are the ones to look at twice. Both took five steps, and both arrived somewhere that is no longer the colour they set out from: an amber that became brown, and a yellow that became olive. The arithmetic passes. The brand does not.

The blue covers 2.29 ratio points across the steps it takes. The yellow spends its first two steps covering less than one point between them, then jumps 1.26 on the step that lands. Same rule, same step size, and the two walks behave nothing alike, which is why this has to be done with a checker open rather than in your head.

Where the tools fit

Where the tools fit. The Contrast Checker takes two colours and gives back the ratio, the pass or fail against both thresholds, and the luminance figures behind the answer — the same working this piece does by hand, for whichever pair you are stuck on. It runs in your browser, so the hex you lift out of a brand book goes into the calculation and no further.

The Palette Generator covers the other half of the job: give it the brand hex and it holds the hue while producing a ramp of darker and lighter steps, so the value that clears the checker is already in the set you are designing from instead of something mixed by eye and hoped over.

What I do with a colour that fails

Darken first. Hold the hue, walk the lightness down five points at a time, and stop at the first value that clears 4.5:1. For a button that is the answer, and for most hues it is close to free, because the brand's identity lives in the hue angle and the hue angle does not move.

If the colour is a yellow or a lime, do the opposite: leave the fill exactly as it is and put near-black on it. If neither works, because the label has to stay small or the colour has to stay a surface, move the brand to an accent and let the text area be a colour nobody is precious about. A two-pixel rule in the exact brand hex is a better ambassador than a button that half the audience cannot read.

And do not try to land on 4.5. The walk overshoots it, and it should: a colour sitting at 4.51 is fragile against a rendering difference, a display profile, or the next person who nudges the hex. Take the first step past the line, then stop looking. The one thing worth carrying away is that both the remedy and the cost come from the hue, so work out which kind of hue you have before you touch anything. A blue can afford to be darkened and a yellow cannot, and no amount of care changes that.