Syntax Kitchen
All kitchen notes

Choosing brand colours that pass contrast checks

A brand colour that looks good on a logo can be unreadable as text. Here is the contrast rule, the formula behind it, and how to design a palette that works in light and dark.

Syntax Kitchen, , 3 min read

Brand colours are usually chosen on a swatch, in isolation. Then they are used for body text, buttons and links, on white and on black, where some of them turn out to be hard to read. It is far cheaper to check contrast while choosing the palette than to repair it after launch.

The rule

The Web Content Accessibility Guidelines (WCAG) measure legibility as a contrast ratio between two colours, from 1 to 1 for identical colours up to 21 to 1 for black on white.

ContentMinimum ratio (level AA)
Normal text4.5 to 1
Large text (about 24px, or 18.66px bold)3 to 1
Interface parts such as input borders and icons3 to 1

Meeting these numbers helps everyone, not only people with low vision. It also keeps text readable in sunlight and on cheap screens.

The formula

The ratio compares the relative luminance of the lighter and darker colour, where luminance is a weighted sum of the red, green and blue channels after converting from sRGB.

contrast.ts
ts
function channel(value: number): number {
  const c = value / 255;
  return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
}
 
function luminance(hex: string): number {
  const [r, g, b] = [1, 3, 5].map((i) => channel(parseInt(hex.slice(i, i + 2), 16)));
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
 
export function contrast(a: string, b: string): number {
  const [light, dark] = [luminance(a), luminance(b)].sort((x, y) => y - x);
  return (light + 0.05) / (dark + 0.05);
}
 
contrast("#ffffff", "#000000"); // 21
contrast("#767676", "#ffffff"); // about 4.54, the lightest grey that passes on white

Where brand colours usually fail

  • Bright accents as text. Yellow, orange and light green look lively but often fall below 3 to 1 on a white background. Use them as fills for buttons and shapes, and put dark text on top.
  • The same colour in both themes. A mid blue that works on white may be too dark on a black background. Define a separate accent for the dark theme and check it on its own.
  • Muted text. Secondary grey text is the most common failure. Check it against the page background, not against pure white.
  • Text on images or gradients. The ratio changes across the image. Add a solid or semi-opaque layer behind the text.

A process that works

  1. Pick the neutrals first: a background, a surface, a text colour and a muted text colour, for each theme.
  2. Check every text colour against every background it will sit on.
  3. Choose the accent last, and test it two ways: as text on the background, and as a button fill with its label.
  4. Record the passing pairs in the design tokens, so nobody has to rediscover them.

Make it part of the build

Contrast problems return whenever someone edits a colour. A small test that runs the function above over your token file turns the guideline into a check that fails the build, the same way a type error would.

A palette that passes on day one and keeps passing is worth more than a clever colour story, and it costs only a few lines to protect.

Next noteStart with a modular monolith

Take a seat.

Tell us what you are hungry for. We reply within two working days.