Visualizing color contrast: a guide to using black and white text on colored backgrounds

Roger Attrill
6 min readNov 21, 2021
A two dimensional chart showing how the contrast of black and white text changes with color hue and saturation

97.4% of website home pages have accessibility failures.

86.4% of home pages have text that is not of sufficient contrast against its background, making it hard for some people to read. As of 2021 this percentage is increasing year on year, not decreasing.

This is the finding of the WebAIM Million project — an annual accessibility analysis of the top 1,000,000 home pages. Low contrast text is by far the most frequent failure of the Web Content Accessibility Guidelines (WCAG).

For people to be able to read text against a colored background, there needs to be a good level of contrast between the two colors. WCAG requires a minimum contrast of 4.5:1 for the presentation of text under 18pt (~24px), and 3:1 for larger text. And that’s only to meet their more commonly accepted AA standard. AAA requirements are 7:1 and 4.5:1 respectively.

With all this in mind I recently had occasion to try and find a foreground color that worked well with a variety of backgrounds. Those backgrounds included black, white, a couple of shades of grey (including a mid grey), and the sort of pale blue you find with highlighted or selected fields and active buttons. I knew the mid grey was going to pose a problem, but I didn’t know how much!

Selected background colors including black, white, off white, mid-grey, and a pale blue

I started off trying a few colors manually and was using the excellent Color Contrast Checker at Snook.ca to get the actual contrast ratios. I just wasn’t able to find a color that had a high enough contrast against all the backgrounds. I was using experience and gut instinct, but it was my gut and that wasn’t good enough. I felt I needed to know how close my choice was to the optimal color — i.e. the highest possible contrast ratio against all the background colors I was using.

I needed numbers.

It’s worth saying there’s a qualitative effect here too — a color that happens to have an equal contrast ratio against black and white might still be improved on from a purely visual perspective, since the way pixels appear when surrounded by on-pixels (white) is different to that when surrounded by off-pixels (black). Generally white on black needs to be a little bolder than the inverse. But still — I needed numbers to know where I stood.

I wrote a program (see end notes) to determine what the optimal color choice was for the highest contrast ratio. It turned out there was a red, a green and a blue that worked equally well so I considered using the blue, although somewhat surprisingly it was less than a 1% improvement over my manual choice. Unfortunately it simply wasn’t possible to get a contrast ratio of more than about 2.13 when using any these colors against the backgrounds that I wanted. In fact, it turns out that having almost any significantly different third background color in addition to black and white immediately hampers your ability to get a suitable contrast ratio against all three.

A contrast ratio of 2.13 simply wasn’t good enough. A solution that met WCAG requirements was not physically possible. Not even close. But now I had my numbers. I took this evidence and pushed back on the constraints, forcing the removal of the mid-grey background from the equation.

That wasn’t then end of the story though.

Having got this far, I was then curious about what backgrounds black or white text works best with, purely from the point of view of color contrast. I adapted my program to generate the charts below, showing the best choice of foreground color (black or white) for varying backgrounds in the HSL color space. To allow for a two dimensional presentation I chose to fix one variable (lightness or saturation) whilst varying the hue in all cases.

Note: Generally I try and avoid pure white text and pure black text as they can appear harsh on the eye. There is such a thing as too much contrast and this has it’s own accessibility issue, making text harder to read for some when compared to a mid-range contrast ratio. So for my calculations foreground colors are either off-white (#F0F0F0) or off-black (#101010) depending on which gives the highest contrast ratio against each background.

In the pictures each color cell has a circle. Circles are either black or white depending on which has the highest contrast, and are shown in three weights:

  • Solid circles: contrast ratio <= 4.5:1
  • Thick circles: contrast ratio > 4.5:1 (WCAG AA compliant for <18pt text)
  • Thin circles: contrast ratio > 7:1 (WCAG AAA compliant for <18pt text)

In this first chart, lightness is a constant 128 (a middle value), while hue varies horizontally and saturation varies vertically with a washed out grey at the top:

A two dimensional chart showing how the contrast of black and white text changes with color hue and lightness
lightness is a constant 128 (i.e. a middle value), while hue varies horizontally and saturation varies vertically

It’s interesting to see that black is generally much better than white, except in the blues and reds. There were no yellows and greens where white was better. Black has a contrast ratio of over 7 for a great swathe of those yellows and greens. White struggles to get the ratio over 7 except in the purest deep blues. The gap in the purples where black again excels is intriguing too.

It’s also worth noting that for some hues like mid-blue, purple, crimson, and orange, it’s very hard to use either black or white to get the contrast ratio above 4.5 (let alone 7) — see the columns where all or nearly all circles are solid.

In the chart below, this time saturation is constant at 255 (highest saturation). Hue varies horizontally as before, but now lightness varies vertically with black at the top and white at the bottom:

A two dimensional chart showing how the contrast of black and white text changes with color hue and saturation
saturation is constant at 255, while hue varies horizontally, and lightness varies vertically

Unsurprisingly, white works on black and black works on white, but again black dominates overall except in the blues. Black dominates most against the yellows, followed closely by the greens. There are few surprises here, but it’s compelling to see the evidence laid out like this. Unlike the constant lightness and varying saturation in the first image, a vast majority of the ratios get above 7 (the thin circles) for these saturated backgrounds.

Making it interactive

These visualizations above are a very useful guide to understanding how the background color impacts the contrast for black and white text and hints at whether black or white text is more likely to be easier to read.

However, I still felt I needed to understand how the contrast for black or white text varies across the whole of the HSL color space, so I went a step further and created an interactive contrast explorer tool based on my earlier charts. This allows me to take an arbitrary vertical and horizontal slice through an HSL color cube and see exactly how the contrasts vary

Screenshot of a program showing a perspective view of a vertical and horizontal slice through the HSL color space

Now this isn’t going to look great as an animated gif which is severely limited in the number of colors it can display, but it’s good enough to show the idea:

Animated gif showing how contrast varies as a program allows the user to vary the position of slices taken vertically and horizontally through the HSL color space

I think that’s satisfied my curiosity enough now — time to move on!

Notes:

The charts were generated programmatically using a Qt C++ program writing directly to an SVG file which can be viewed in any browser and can show tooltips for the exact color and contrast values.

For speed of rendering the interactive HSL explorer uses 1024 pre-generated images converted from similar SVG output but which had been transformed using a matrix to create the angled perspective. An image was generated for each of 32 horizontal and 32 vertical slice positions. No 3D graphics here, just SVG transforms and images. Sorry to disappoint!

The contrast ratio was calculated as (L1 + 0.05) / (L2 + 0.05), where

--

--

Roger Attrill

UX Specialist for Linguamatics. Passionate about users (new and used), visual design, data viz, losing silos, the little details & the big picture.