Using Algorithms to Meet Accessibility Requirements for Color Contrast
My name is Ben Cole and I am a Front End Engineer on the Core UI design systems team. Our team creates the building blocks which designers and engineers use to make user interfaces for Twitch products. While doing this foundational work, we try to balance things like accessibility considerations, brand guidelines, and the creativity of the Twitch community.
A big part of Twitch is community and self expression. We wanted to bring that into our products, and let our creators pick colors to customize UI elements which represent their channel and identity. Giving users the freedom to pick colors creates a challenge: how do we make sure that the chosen colors are clearly visible?
Which of the following color examples has enough contrast to be readable? How do web designers around the world know if the colors they pick are readable?
The Web Content Accessibility Guidelines (WCAG) are a set of standards and best practices that help UX designers and engineers create accessible websites. They help us pick colors that can be clearly seen by everyone, including people with moderately low vision or a color vision deficit (WCAG 2.1 specifies that colors must have a contrast ratio of at least 4.5 to 1). From the example above, only the last two sets of colors meet the WCAG 2.1 requirement.
In order to let people pick colors, we needed to find a way to balance the freedom to pick any color for UI elements with the minimum contrast requirements defined by WCAG. To tackle this challenge we created a system which allows users to select any color, and generates a color palette which designers can apply to user interface elements.
Where is this used on Twitch?
Streamers can choose a profile color under the Channel Settings page of the streamer dashboard. The chosen color gets used to decorate different elements of their channel.
Below is a Hype Train banner, which uses the creator’s brand color as the background fill to indicate progress:
Below is the channel schedule page, showing the currently scheduled stream event with the creator’s brand color as both the background and as an accent color for other calendar events.
There are other features which use the streamers chosen color in different ways as well, these are just a few examples.
How it works
First, the streamer picks one color to represent their brand. Below is the color picker UI which streamers can use to select a color for their channel brand.
After they do that, we generate a list of similar colors based on the selected color. These colors can be used together as a text / background color pair and still be readable, because we do some math to ensure that the colors we generate meet WCAG contrast requirements.
From this list of colors, we create “design tokens” – named colors which our designers and engineers can apply to different UI elements. This allows us to specify that some text will receive a generated color, while still guaranteeing that it will meet certain contrast requirements – without knowing exactly what the color will be ahead of time!
The logic behind the algorithm
When the user selects a color, it is provided to us in hexadecimal format (i.e. #9147FF). This format is actually just a different way to represent an RGB color (i.e. rgb(145, 71, 255)). In the RGB color format, each of the three numbers represents how much Red, Green, and Blue should be mixed together.
My first approach to generate different shades of a color was to adjust the values of the RGB color, increasing or decreasing each of the digits. However, this approach actually didn’t provide enough fine tuned control in steps between colors, and adjusting RGB values didn’t match the mental model of how we wanted to change the color. So instead, the color is converted into a format called HSL (Hue, Saturation, Lightness). This format is much more intuitive to modify, because you can adjust a numeric value which represents saturation and lightness as a percentage and use much smaller step values. (A caveat is that converting the color back to RGB may involve rounding some digits)
One thing that made the algorithm particularly tricky to implement is that different colors, like yellow or blue, are perceived differently by different people. The contrast formula provided as part of WCAG 2.1 uses a method that calculates “luminance contrast” (contrast that is independent of color perception). Even if you make exactly the same numeric adjustment to the “lightness” value to two different colors, it might have a different effect on the contrast of each color. For example, the image below shows yellow and blue color pairs, each with exactly the same lightness adjustment between each step but a different result for computed contrast:
What this means for our algorithm is that it needs to account for differences in color hue, and that it will generate a different “lightness curve” depending on the chosen color.
It is worth mentioning that there are other color spaces, such as CIELAB, which account for differences in human perception of colors.
Collaborating with Designers
One thing we learned is that, although we can generate a set of colors which technically meets the contrast requirements, it might not be aesthetically pleasing and match the intent of a user’s chosen color. During development, we collaborated closely with UX designers. We created test pages to demo different possible algorithms and had our UX designers test out picking different colors. We iterated on the algorithm based on feedback about specific colors.
For example, one piece of feedback we received from our designers was that when picking very dark and very light colors, the algorithm was initially generating colors that felt too saturated and didn’t feel like they matched the intent of the chosen color. To address this, we implemented an easing curve which reduces saturation if the chosen color is very dark or very light.
Evaluating engineering trade-offs between using an existing open-source implementation and writing your own code involves considering how much customization may be required. In our case, the choice to write our own algorithm allowed us to have greater control over the end result and to fine-tune the output to work well with the rest of our color palette.
One of the observations which was made while working on this was that certain color pairs sometimes felt more difficult to read, even though the WCAG 2.1 contrast formula said that they had superior contrast. Since we originally developed this system, progress has been made on WCAG 3 which includes a new method of computing contrast using the Advanced Perceptual Contrast Algorithm (APCA). This updated formula takes into account details like font size, if a color is used for text vs a background, and incorporates more modern research on color perception and color vision deficiencies.
Additionally, support for a new CSS property called “accent-color” has been added to some browsers! This property allows developers to specify any custom color to be used on components like checkboxes or range sliders. Then it is paired with contrasting dark or light colors, according to the browser’s algorithm, ensuring that minimum contrast is met.
I personally hope we can continue to see algorithms used to create user interfaces which support greater flexibility and user customization, while also ensuring accessibility requirements are included as a default part of the user experience.
Twitch should be a place where everyone is welcome and able to participate. As we create tools to give users more control and customization, we must also consider how different users will interact with the content. If you have a suggestion to improve accessibility on Twitch, please share it on our Uservoice forum under the “User Accessibility” category!
This blog post was updated on Dec 6, 2021 to include mention of the differences between the WCAG 2.1 contrast formula and the upcoming WCAG 3 formula for computing color contrast.