Insufficient color contrast is one of the most common issues when talking about Web Accessibility. In fact, it was the most common problem found in The WebAIM Million analysis, adding up to over 60% of all the issues found.
There are many great online tools that can help you check the color contrast, but in this post I am going to show you how to create your own. There are good reasons to create your own color contrast checker:
- It is a small programming project;
- It will help you understand how color contrast works; and
- It will allow for some creative process.
But before going to the coding part, let's see some theory...
What is color contrast?
That's a great question.
To put it in a simple way, the contrast between two colors is the difference between both of them. On the web, you want to have (enough) high contrast between the foreground and background colors so they provide a better experience for people with vision impairments and color deficiencies.
The contrast ratio between two colors is calculated using a mathematical function that we will see below. The resulting value of that calculation will go from 1 to 21 (normally represented as 1:1 or 21:1 respectively). If both are the exact same color, the contrast ratio will be 1; The highest contrast is between white and black, and exact opposites in the color wheel (complementary colors) will have high values too.
What are WCAG color contrast requirements?
There used to be an unique contrast requirement of 4.5:1, but WCAG 2.1 defined different color contrast requirements that depended not only on the colors, but also on the size and weight of the element that is being tested or the level of accessibility to be achieved.
That makes sense. Smaller text needs higher contrast than larger or bolder text. Also in order to achieve a higher standard level of accessibility (AAA) the contrast must be higher than for other levels (AA).
The general idea remains: text color contrast must be 4.5:1, but there are some exceptions when text is larger (18 points or higher) or bold (14 points or higher) in which case a color contrast ratio of 3:1 is enough for AA-level accessibility.
For achieving a AAA-level accessibility color contrast, the ratio numbers go up to 7:1 for regular text, and 7:1 for large text.
Component | AA-level | AAA-level |
---|---|---|
Small text (<18pt or >=14pt bold) |
4.5:1 | 7:1 |
Large text (>=18pt) |
3:1 | 4.5:1 |
What is luminance?
Before getting any deeper into the contrast ratio and how to calculate it, we must introduce the concept of luminance and relative luminance:
[Relative luminance is] the relative brightness of any point in a colorspace, normalized to 0 for darkest black and 1 for lightest white.
Calculating the relative luminance may be a bit tricky and look complicated, especially if we check the logic included in the WCAG definition:
For the sRGB colorspace, the relative luminance of a color is defined as L = 0.2126 * R + 0.7152 * G + 0.0722 * B where R, G and B are defined as:
- if RsRGB <= 0.03928 then R = RsRGB/12.92 else R = ((RsRGB+0.055)/1.055) ^ 2.4
- if GsRGB <= 0.03928 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4
- if BsRGB <= 0.03928 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4 and RsRGB, GsRGB, and BsRGB are defined as:
RsRGB = R8bit/255
GsRGB = G8bit/255
BsRGB = B8bit/255
The "^" character is the exponentiation operator. (Formula taken from [sRGB] and [IEC-4WD]).
I know. This text can be intimidating, but once it is translated into code, the formula is not as messy as it looks.
Contrast Ratio Formula
This will be the last step (at least the last big calculation step). The contrast ratio formula can also be found in the WCAG definition:
CR = (L1 + 0.05) / (L2 + 0.05), where
- L1 is the relative luminance of the lighter of the colors, and
- L2 is the relative luminance of the darker of the colors.
It is much simpler than the luminance one... and the last step before checking against the values defined by WCAG. So here it is where the theory ends, and the coding begins.
Building the color contrast checker
Now that we have reviewed the general theory around color contrast, luminance, and how to calculate it, we can start building our color contrast checker.
It is going to be something small and simple, a minimum viable product (MVP), that can be extended later. And to develop it we are going to use HTML and JavaScript (without a framework).
We are going to have two input fields and a button. The inputs will take a valid HEX color in full format (e.g. #003366) or shorthand format (e.g. #036) and, when we press the button, it's going to tell us if the colors have enough contrast to be considered accessible.
Something that translated into HTML could look like this:
<input type="text" id="color-1" value="#000000" />
<input type="text" id="color-2" value="#ffffff" />
<button>Calculate Color Contrast</button>
<div id="result"></div>
The JavaScript code should be straightforward. The steps to follow are:
- Break the colors into RGB
- Calculate relative luminance for each color
- Calculate the contrast ratio value
- Compare the value with WCAG requirements
Break the colors into RGB
To break the colors from HEX to RGB, we can use different methods or libraries. For simplicity (and to keep everything in vanilla JS), we opted to use the hexToRgb
function created by Tim Down:
function hexToRgb(hex) {
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
Based on the HTML above, the JavaScript to read the colors and transform them into RGB format would be something like this:
// read the colors and transform them into rgb format
const color1 = document.querySelector("#color-1").value;
const color2 = document.querySelector("#color-2").value;
const color1rgb = hexToRgb(color1);
const color2rgb = hexToRgb(color2);
Calculate luminance for each color
The relative luminance calculation algorithm looked intimidating in the theory section, but once coded it looks much simpler. For example, kirilloid summed it up in 10 lines of code:
function luminance(r, g, b) {
var a = [r, g, b].map(function (v) {
v /= 255;
return v <= 0.03928
? v / 12.92
: Math.pow( (v + 0.055) / 1.055, 2.4 );
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
We can use that formula with our previously calculated RGB colors, and that way we will get the luminance:
// calculate the relative luminance
const color1luminance = luminance(color1rgb.r, color1rgb.g, color1rgb.b);
const color2luminance = luminance(color2rgb.r, color2rgb.g, color2rgb.b);
Calculate the contrast ratio
The next step is calculating the contrast ratio. For this, it is important to point out that it doesn't matter the "order" of the colors. That means that the ratio for two colors is the same independently of which one is in the background or the foreground.
For example, the contrast ratio for white over black will be the same as the contrast ratio for black over white.
As we explained in the theory part, the luminance is a value between 0 and 1. That means that the color with higher luminance (the darkest one) will be used as the dividend in the formula:
// calculate the color contrast ratio
const ratio = color1luminance > color2luminance
? ((color2luminance + 0.05) / (color1luminance + 0.05))
: ((color1luminance + 0.05) / (color2luminance + 0.05));
And with that, we have finished all the big calculations for the contrast ratio. The only thing left is to check the values against the accessibility requirements.
Compare with WCAG requirements
We have the color contrast ratio and -from the theory that you read and didn't skip- you know that the minimum WCAG requirements are:
- 0.14285 (7.0:1) for small text in AAA-level
- 0.22222 (4.5:1) for small text in AA-level, or large text in AAA-level
- 0.33333 (3.0:1) for large text in AA-level
So the missing code is just showing the results. Keeping it as simple as before, we could do something like this:
// show results depending on WCAG requirements
const result = `
AA-level large text: ${ratio < 1/3 ? 'PASS' : 'FAIL' }<br>
AA-level small text: ${ratio < 1/4.5 ? 'PASS' : 'FAIL' }<br>
AAA-level large text: ${ratio < 1/4.5 ? 'PASS' : 'FAIL' }<br>
AAA-level small text: ${ratio < 1/7 ? 'PASS' : 'FAIL' }
`;
document.querySelector("#result").innerHTML = result;
After wrapping all these steps in a formula, and attaching it to click event of the button, our contrast checker looks like this:
The result is really simple but really functional too... And now is when the real fun begins.
Adding some pizzazz
Right now we have all that we need for a color contrast checker: inputs to read the colors and the logic to calculate the contrast ratio... but it is really basic. And here is where your creativity comes to play.
Your checker doesn't have to be the same as all the other ones that are online. Add your own signature to it, and adapt it to your needs. As with most things in Web Development, the only limit is your imagination:
- Most online checkers only take HEX colors but you use HSL? Add HSL to your checker! Or use tools/plugins to parse ANY color format!
- Want to offer suggestions and alternative palettes if the contrast is not accessible? That would be incredibly helpful for your users!
- Animations, interactivity, and all that jazz? Anything that makes it more usable and attractive should be welcomed!
If you end up developing your very own color contrast checker, please share it in the comments. I will love to see them.
For example, below you can find a color contrast tool that I created. It is not too fancy, I developed it in vanilla JS, added the possibility of entering HEX or RGB values, and spiced it up with animations so the SVG character would smile more or less depending on the accessibility level.
Top comments (11)
How to incorporate opacity while calculating contrast? One possible solution might be blending opacity layer to bg color and use these functions?
That is an interesting question. There are functions that mix colors, maybe using those we can combine alphas (or come up with our own), but that one is always a messy one (as there may still be some background color that we are not considering)
Thank you for posting this. :)
Thanks. Gonna see how I can work this in my design system.
Thanks ! I'm no expert in image processing, but you are x)
So I was looking for any simple methods to assess image quality, I know this is a hard subject, but if we reason by exclusion I think we can find interesting ways to spot low quality images.
For instance, first rule I could find is brightness, if it is say above 230 or bellow 20 the image is of bad quality.
In the same way, do you have any scores to suggest to spot bad quality images ?
I searched a lot and I could only find ML models which are tedious to implement and deploy.
Note from w3.org/WAI/WCAG21/Techniques/gener...
Before May 2021 the value of 0.04045 in the definition was different (0.03928). It was taken from an older version of the specification and has been updated. It has no practical effect on the calculations in the context of these guidelines.
Hey thanks for the post.
What should we do if we have
rgba
color?Awesome ! Thank you for this article
Hero! this is awesome thanks for sharing :)
Thanks!
This is fantastic! Thank you for sharing!