In early 2019, I wrote a post about designing an accessible color scheme for Envoy’s web dashboard with specific techniques I used to create various shades of all the colors we needed to build our UI.
We’ve been moving quickly to ensure our customers can return to work safely, but in terms of color accessibility, we backslid a bit.
So here is that story and what we did about it, including brand new techniques to help you build an accessible color scheme, whether it’s the first time around or not 😁
Long story short, we went through a rebrand and our brand colors changed. We love our new palette—a lot of thought and care went into choosing it (shoutout Kelly and Amy!). We were so excited that we quickly swapped some of the old product colors for the new brand colors with the goal of rolling out the new brand inside the product.
What we realized a little bit too late, however, was that brand color palettes and product color palettes are two very different things.
Not all of the brand colors were WCAG AA compliant for the smaller text common on Envoy’s web dashboard, even if those same colors were just fine for the larger copy on Envoy’s marketing website.
We also needed more colors than those included in the brand palette to represent common product states like warning, error, and success. In our excitement to roll out the new brand, some new colors for these states were chosen, but most of them were not accessible, and after a few months we realized that some shades didn’t feel like they fit into the new brand palette all that well.
We realized we needed to go back to the drawing board and build on top of our beautiful brand palette to create an expanded color system for our products that would ensure accessible content and provide flexibility for all our states and common components. We had work to do!
How we built a new, bigger, more accessible system
Last time around, I created four shades of each main UI color, and six shades of grey.
I built all the colors on top of a white background and chose shades greater than or equal to common accessibility contrast ratio breakpoints of 7:1, 4.5:1, and 3:1. I also included a lighter shade to use as a background color with ~1.1:1 contrast.
This system ensured that text would be accessible on top of a white background, which is the majority of Envoy’s dashboard UI.
But over time, we realized that when we stacked colors in the system on top of each other, we weren’t able to achieve those contrast ratio breakpoints.
This time around, I knew I wanted to create more shades of each color to provide more flexibility in how elements of different colors could be combined in components and still maintain accessibility.
My breakthrough came when I read about the techniques used by the U.S. government web design system (USWDS), a source I often turn to for great examples of how to build accessibly.
They created a genius system to generate color families made up of 10 “grades” or shades of each color, numbered 5 through 90 (white is always grade 0 and black is grade 100).
This system guarantees accessibility with it’s “magic number” method: if you stack one grade over another, as long as there is…
- 50 points difference between two color grades ensures greater than 4.5:1 contrast
- 40 points difference between two color grades ensures greater than 3:1 contrast
The USWDS documentation lays out exactly how to build color families this way in a table that shows the precise relative luminance values each grade must fall between to make the magic number system work.
This is a great resource, but if you’re anything like me, you also might not have ever heard of relative luminance. Don’t worry, I got you.
While it may be oversimplifying, think of luminance values as how bright a color is from 0 to 1. White is the brightest color (1) and black is the darkest color (0). A larger number equals a lighter color.
Knowing this, the table above starts to make more sense; for example, Grade 5’s luminance can’t be any darker than 0.850 or any lighter than 0.930. Those luminance values are quite close to 1, so we can visualize that Grade 5 is very close to white, a very light color.
I also learned that the formula for calculating contrast ratios relies on these luminance values:
(L1 + 0.05) / (L2 + 0.05)L1: the relative luminance of the lighter of the colors
L2: the relative luminance of the darker of the colors
Because I wasn’t as familiar working with luminance values, and I had a hard time finding a tool to quickly translate a hex value into a luminance value, I decided I would instead translate the USWDS table into contrast ratios (as compared to white) using the formula above.
Grade 5’s luminance value can be no darker than 0.850 and no lighter than 0.930What contrast ratio would a color with a luminance of 0.850 have against a white background?We know the formula for contrast ratio is lighter divided by darkerWe know that white has a luminance of 1Applying these known values to the contrast ratio formula, you get:(1 + 0.05) / (0.850 + 0.05) = 1.17:1Repeat for 0.930(1 + 0.05) / (0.930 + 0.05) = 1.07:1Solved: Grade 5 can be no lighter than 1.07:1 against a white background and no darker than 1.17:1
This helped me produce a resource in terms I could understand faster, and allowed me to use existing Figma plugins to more quickly calculate if my color was in range.
Following the table above could absolutely empower you to quickly create color families. In Figma, it’s as easy as firing up Contrast, drawing some rectangles, and adjusting HSL values until each color falls into the right range of contrast values (and you like the way it looks).
You can absolutely stop reading here, follow those ranges and create amazing and accessible color schemes.
I will only give you one more pro tip to do this in the browser version of Figma, or change your color space in desktop Figma, because there can be substantial differences in color between the two, and you’ll likely want to know what the colors will look like in the browser before committing to them.
But if you’d like to continue reading, I will show you how you can adjust the USWDS ranges to make them lighter or darker according to your preferences. It’s gonna get mathematical!
I personally found that the USWDS ranges, as written, produced color schemes that were a bit darker for Grades 60+ than I wanted; I wanted to get as much vibrancy as possible even from my darker shades.
Using the same formula for calculating contrast ratio, I set out to make some adjustments.
I started with Grade 60. In the USWDS table, Grade 60 had a contrast ratio range of 6:1–7:1. I wanted to lighten up the low end of the range to 5.5:1.
Since there’s 40 points difference between Grade 60 and Grade 100, I knew that Grade 60 would need to have at least 3:1 contrast against black (Grade 100).
What’s the maximum contrast allowed for Grade 60 to that can still achieve 3:1 vs. black?• Formula: (L1 + 0.05) / (L2 + 0.05) = contrast [lighter divided by darker]
• Black has a luminance of 0x / 0.05 = 3
3 * 0.05 = 0.15
0.15 - 0.05 = 0.1 (maximum luminance value for Grade 60)
Convert that luminance value back into contrast against white:(1 + 0.05) / (0.1 + 0.05)
1.05 / 0.15 = 7:1 (maximum contrast value for Grade 60)
So I must leave the upper bound of the range the same: 7:1.
Grade 60 has a 50 point difference with Grade 10, so what’s the darkest Grade 10 can be to still achieve 4.5:1 versus Grade 60?
Assume the lightest Grade 60 can be is 5.5:1What's the luminance of a color with a contrast of 5.5:1 against white? (manipulate the same formula)1.05 / 5.5 = 0.191 - 0.05 = 0.141Solve for Grade 10 luminance:x / 0.191 = 4.5
0.191 * 4.5 = 0.86 - 0.05 = 0.81 (maximum luminance value for Grade 10)Convert that luminance value back into contrast against white:1.05 / 0.86 = 1.221:1 (maximum contrast value for Grade 10; can't be darker than this)
And finally, Grade 60 has a 40 point difference with Grade 20.
1.05 / 5.5 = 0.191
0.191 * 3 = 0.573
1.05 / 0.573 = 1.83:1 (maximum contrast value for Grade 20; can’t be darker than this)
Essentially, by using the formula for calculating contrast ratio and the concept of the magic number, you can continue to make any adjustments you’d like to the grade ranges. I adjusted most of them just slightly, ending up with this table of values:
I think these changes were noticeably helpful to make the darkest few grades more vibrant and less like “almost black” shades, which works better for our product.
I will note, though, that USWDS’s system supports level AAA compliance with a magic number of 70, which I did not test my adjusted system against, so if you are aiming for a higher accessibility standard, you may want a darker range of colors.
After lots of math and hours of tiny HSL adjustments, this is where I ended up:
The absolute best thing about this system is its flexibility and “stackability”—as long as the magic number is met, you can stack any color over any other color and accessibility is guaranteed.
This new system includes all our brand colors marked at their appropriate grade, so all brand colors can be used in the product accessibly, as long as the magic number is met.
You’ll notice that not all grades are defined. For our brand color (red) we decided to only create a few adjusted values to meet accessibility breakpoints and not to create lighter shades which won’t look visually similar to our brand color.
For our chartreuse, orange, and green colors, we use those rarely for warning, error, and success states, so darker shades weren’t necessary at this point—however, it’d be easy enough to go back later and define more grades.
Overall, our new color system gives our designers incredible flexibility in building new components, and guarantees that our products are accessible. What more could you ask from a color scheme? 🌈
Have thoughts on this topic? Sound off in the comments or chat to me on Twitter!
Thanks for reading! Be sure to visit envoy.design and subscribe to get notified when we publish something new. Or check out my previous article, How to build management skills before you’re a manager