Better color contrast on the web
A little bit of background.
The W3C chartered the Web Accessibility Initiative as the central place to gather information, research, best practices, and standards (recommendations in W3C talk) for accessibility on the web.
The best-known resource generated by the WAI is the Web Content Accessibility Guidelines (WCAG) recommendation. The current version of WCAG is 2.1 with a draft of version 2.2 expected to become a recommendation soon and an earlier draft of version 3 as an early working draft.
The current version of WCAG provides two guidelines regarding accessibility:
Success Criterion 1.4.3 Contrast (Minimum) The visual presentation of text and images of text has a contrast ratio of at least 4.5:1
and
Success Criterion 1.4.6 Contrast (Enhanced) The visual presentation of text and images of text has a contrast ratio of at least 7:1
Since their introduction WCAG contrast requirements have done an adequate job of guiding what should is good contrast and what isn't.
But things change. The monitors we use, the devices we view web content on and the research on color perception have all changed since WCAG 2 was first introduced.
In Please Stop Using Grey Text Andrew Sommers identifies problems with the way WCAG 2 measures contrast. Some of the biggest issues, to me, are:
- A one-size fits all approach to contrast doesn't work
- Different font styles require different contrast levels (thinner fonts require more contrast)
- Different font sizes require different contrast levels
- People may need different levels of contrast to make text readable for them
The W3C Accessibility Guidelines (WCAG) 3.0 introduces APCA, a new way to measure color contrast that follows modern color and mathematical theories.
APCA addresses the drawbacks of the WCAG 2 contrast method.
Like color, contrast is not “real”, it is a perception and is more a result of how your brain interprets visual differences. It is not a simple measure of the distance or difference between two colors. Like all perceptions it is context sensitive, meaning what is around it and its purpose affects how you see it. Contrast is also substantially affected by “spatial frequency” which essentially means font size and weight, and is closely related to our brain’s lightness perception (aka luminance contrast). When it comes to color contrast, as in hue/chroma/saturation, the effect is less relevant to readability. High lightness/darkness contrast is required for fluent readability at best speed and comprehension, especially small body text in columns or blocks. Above a certain amount, contrast constancy may come into effect wherein further increases in a mathematical contrast value does not have an effect higher perceived contrast for readability. A high “spatial frequency” means smaller and thinner letters. Smaller, thinner letters or graphics lowers the perceived contrast. As a result, the lightness/darkness difference between text and the background color must be increased to compensate for small thin fonts.
- Fluent readability refers to critical contrast which is that needed for best reading speed and comprehension. Dr. Bailey & Lovie-Kitchin’s studies showed, along with Dr. Legge and other recent studies, contrast must be at least ten times the contrast sensitivity threshold (CS) which is the point of “just noticeable differences” (JND). Twenty times is preferred for adequate contrast reserve above the critical contrast.
- Spot readability means being readable without significant effort, though not necessarily at the best speed or accuracy. In this case the contrast needs to be three times that of the JND.
- Various forms of visual impairment include more than acuity, which is the ability to focus the eyes to a sharp clear image. Contrast sensitivity related impairments involving the eyes or brain may have an even greater effect on overall vision. These factors define the area of “supra-threshold critical contrast for readability.” We refer to this as “readability contrast” for simplicity. A similar supra-threshold exists for acuity in terms of font size, which is separate but in addition to the font sizing as related to spatial-frequency-driven contrast.
So we have an existing contrast algorithm that has been around for 14+ years with known shortcomings and a new algorithm that will become part of the next version of the specification (WCAG 3) that will not see the light of day for a few more years still.
So how do we leverage this new color contrast method in code?
Measuring contrast using color.js #
Lea Verou and Chris Lilley's color.js library provides tools to test contrast between two colors.
Color.js provides eight different contrast methods to test with. The ones I'm most interested in are: WCAG 2.1 and APCA (as proposed for WCAG 3).
See Contrast for more information on the methods available to test color contrast with color.js.
We first define the color space that we want to use and the colors we want to test
// Initialize colors
const text = new Color("p3", [0.9, 0.8, 0.1]);
const background = new Color("slategrey");
Then we test the contrast of the two colors using either its dedicated function name, or the general contrast() function.
The first set of values use the WCAG 2.1 contrast algorithm.
// WCAG 2.1 contrast
const contrast2a = text.contrastWCAG21(background);
const contrast1a = text.contrastWCAG21(background);
// These values are equivalent
const contrast1 = text.contrast(background, "WCAG21");
const contrast2 = background.contrast(text, "WCAG21");
When testing contrast using the WCAG2.1 produces the same results regardless of the order we test.
// Text on background
console.log(contrast1);
// -> 2.5052963014717715
// Background on text
console.log(contrast2);
// -> 2.5052963014717715
The next tests use the APCA algorithm. In this swapping the order of the colors tested produces different results.
// APCA contrast
const contrast2 = background.contrast(text, "APCA");
const contrast3 = text.contrast(background, "APCA");
// Background Against Text
console.log(contrast2);
// -> -41.00861317821858
// Text Against Background
console.log(contrast3);
// -> 38.18208871861666
So, we have values but what do they mean? Since the values and their meaning change when working with APCA we also need to understand what these values mean.
I found this Twitter thread from Dan Hollick very helpful in understanding what the APCA values mean.
I will try to unpack some of the content of the thread.
In light mode, the higher the positive value is, the higher the contrast.
It uses a 0 to 100 scale.
Dan provides a set of equivalencies between WCAG 2 and APCA
WCAG 2 | APCA | Meaning |
---|---|---|
1.25: 1 | 15 | Minimum for non-text elements |
1.5: 1 | 30 | Rough Equivalency Absolute min for any text |
3:1 | 45 | Rough Equivalency Min for large text (the old 3:1) |
4.5:1 | 60 | Rough Equivalency Min for body text (the old 4.5:1) |
7:1 | 75 | Rough Equivalency Min for body text (the old 4.5:1) |
10: 1 | 90 | Preferred level for body text |
But see also the following figures that explain the APCA contrast values.
While WCAG 3 won't become a recommendation for a while, we can use APCA in our work today.