How We’re Building Accessibility Into Amplitude’s Color System

Why “blue-526” is better than “blue-500”

Jack McCloy
Amplitude Engineering

--

Working with color is challenging. Not only does it take a great deal of skill to choose and apply colors effectively, but it also requires a bit of math to ensure those colors are accessible. It’s not enough for your color combinations to be visually pleasing, they also need to have enough contrast. This is especially important for users with visual impairments — if your site doesn’t have enough contrast, they won’t be able to read or use it.

By leveraging a color’s luminance (a.k.a. brightness) and applying just a bit of math, I’ll show you how we generate color names that are descriptive, simple to work with, and make it much easier to guarantee your color combinations are accessible.

A Bit of Background

At Amplitude, we’ve been working on improving our product’s accessibility. A digital product that isn’t accessible to people with visual impairments can be as exclusionary as an office building that isn’t wheelchair accessible¹. We feel a responsibility to build product that is accessible to all users, and it’s a challenge we’re excited to take on.

We believe most designers and developers want to ship accessible product. We know this is true at Amplitude. When we fall short of our accessibility goals it’s almost always a failure of execution rather than a failure of intent.

So rather than viewing accessibility as an add-on to every feature we ship, we’re approaching it as as a systems problem. This means we’re constantly on the lookout for ways to modify the way we build things so it’s easier to create accessible experiences and harder to accidentally ship inaccessible ones.

We recently developed and are starting to implement a color-numbering system that makes it much easier for designers and developers to identify accessible (and inaccessible) color pairs from their names. This system is already helping us to use color more confidently and to identify parts of our product that don’t meet our standards for accessible color use.

In the rest of this post I’ll walk you through the naming system we left behind, why that system made accessibility more difficult than it needs to be, and how we changed it to make shipping accessible experiences easier.

The Ordinal Scale We Started With

The color system we had been using employed names made up of two parts — a descriptive color name like blue, teal, or gray as well as a number between 0 and 1000 that represents the lightness or darkness of the color.

We didn’t invent this naming system. It’s the color naming system used by popular libraries like Tailwind CSS, Ant Design, Material UI, and countless large and small companies².

Amplitude’s pre-update color names

The descriptive names (blue, teal, gray) in a system like this are pretty straightforward — they convey information about the color’s hue (or, in the case of grays, the fact that it’s desaturated).

But what about the numbering system being used? The technical term for this type of numbering system is an “ordinal scale”. With an ordinal scale the items “have natural, ordered categories and the distances between the categories is not known [emphasis added].”

This means that the numbering corresponds to a color’s luminance, but not in any absolute way. The numbers tell you that blue-600 is darker than blue-500, which is darker than blue-400, but it doesn’t tell you how much darker. It turns out that the difference in relative luminance between blue-400 and blue-500 is nearly twice the difference between blue-500 and blue-600.

Additionally, the numbers aren’t comparable across color sets. There’s no guarantee that three colors with the same number, like blue-600, teal-600, and gray-600, would have similar luminance. And they don’t.

To be clear, this isn’t only a problem with our ordinal scale — it’s a limitation of ordinal scales in general. In Material UI, for example, red-200, their third lightest red variant, is actually darker than yellow-600, a darker-than-average yellow. Ordinal scales, by their very nature, are unable to communicate anything absolute about luminance.

This is a problem if you’re trying to use color accessibly because you can only know if a color pair is accessible by comparing the two colors’ luminance.

A Step Towards Accessible Color Naming

In 2018 Kevyn Arnott, then working at Lyft, wrote about the new color system his team was using.

At the time, this section caught a lot of people’s attention, including mine:

We made accessibility a cornerstone of our new color system. We wanted to remove the need to manually check color contrast using third-party tools, and we needed to make it dead-simple for everyone to create accessible products.

To solve this, we leveraged what we had already done with color naming and selection. Using our algorithm, we made our color lightness-to-darkness consistent across color hues, so that every color 0–50 is accessible (4.5:1) on black, and every color 60–100 is accessible (4.5:1) on white.

Created from an illustration originally on the Lyft Design blog

The approach here blew my mind. It wasn’t trying to solve the problem with rules or tools. It was, rather, a systems-based solution that can clearly communicate, for any color, what color text can be put on top of it.

But this system isn’t without limitations. We’ve already established that ordinal scales can’t communicate anything absolute about luminance, and thus can’t tell you anything about accessibility. Since the Lyft color system clearly uses an ordinal scale, you might wonder how it’s able to communicate any information about accessibility. Where’s the magic?

The sleight of hand is that they’re really using TWO ordinal scales, not just one. The first ordinal scale has a range of 0–50 and is used for colors that can be paired with black. The second ordinal scale has a range of 60–100, for colors that work with white. The limitations are still there, which is why the numbers can tell you only if a color is accessible compared to pure black or pure white, and even then only using the AA contrast ratio standard.

This limitation might not seem like a big deal, but most apps don’t use pure black and white text — at least not everywhere. And some apps aim for the stricter AAA contrast standard of 7:1.

It got me wondering if there could be a color naming system that would work for all colors, and across all contrast ratios. As it turns out, there is!

A Better Way

This brings us to the improved numbering scale we developed and are now using at Amplitude. Rather than beginning with an ordinal scale, we’re using a ratio scale that can communicate information about luminance that is specific and precise.

This increased precision is reflected in the numbers we now use. What we used to call blue-500 is now blue-526, for example. This increased precision allows us to make luminance comparisons across color sets and know, for example, that teal-621 is lighter than blue-526 and darker than gray-689³.

Most importantly, it makes it much easier to figure out what colors can work together accessibly, using any contrast ratio. If the difference between two numbers is ≥ 495, the two colors have a contrast ratio of at least 4.5:1. If the difference is ≥ 640, the two colors have a contrast ratio of at least 7:1. And if the difference is ≥ 361, the contrast ratio is at least 3:1 (a contrast ratio that’s AA accessible for sufficiently large text).

In the next section I’ll cover the math we used to generate these numbers. If you’re not interested in the math and just want to start using the system you can copy this spreadsheet that does all the math for you.

The Math

For any fully opaque color or shade follow these steps:

Step 1: Find the color’s relative luminance. It will be a value between 0% for pure black and 100% for pure white. I’ve been using this website to calculate relative luminance of any color to four decimal places.

Step 2: Add 5 to the relative luminance to get the adjusted relative luminance. Take the number from Step 1 and add 5 to it. This will give you a value between 5 for pure black and 105 for pure white. This “adding of 5” is in accordance with the formula WCAG uses to calculate contrast ratios. We’ll call this number the “adjusted relative luminance”.

Step 3: Divide the adjusted relative luminance by 5 to get the scaled relative luminance. Take the number from Step 2 and divide it by 5. Now you’ll have a number between 1 (pure black) and 21 (pure white). We’ll call the number generated in this step the “scaled relative luminance”. When you have two of these numbers (representing two different colors) they can be used to represent the pair’s contrast ratio — the ratio between black and white, for example, would be 21:1.

Step 4: Calculate log2(x) of the scaled relative luminance to get the binary log of scaled relative luminance. Here’s where things start to get fun. Take the number from Step 3 and calculate its binary log (a.k.a. its log base 2). You can do this with any calculator, or use a website like this one. You’ll get a number between 0 (pure black) and 4.38238… (pure white). By applying the binary logarithm to the value from Step 3 we’re mapping it in such a way where a standard increase in value corresponds to a standard increase in contrast ratio — the thing we care about. This is what makes it possible to know the contrast ratio of two colors using subtraction as opposed to division.

Step 5: Multiply the binary log of scaled relative luminance by 1000 / log2(21) to get your final number. Take the number from Step 4 and multiply it by 1000 / log2(21), a scaling constant used to map the values from Step 4 onto a 1000-point scale. This will leave you with a number between 0 (pure black) and 1000 (pure white). Round this number to the nearest whole number and use it in your color name!

In Summary

Using color names that communicate specific accessibility information can make the task of identifying and using accessible color combinations much easier for your team, as it has for ours. Naming colors in this way allows your designers and developers to be more confident in the accessibility of the product experiences they build, and eliminates the need to rely on contrast-testing tools or plugins.

Our hope is that you’ll find this approach as useful as we have, and that it enables you and your team to build products that are more inclusive.

Interested in working together on projects like this one? Amplitude is looking to hire a design systems engineer — maybe that could be you! Apply here!

Have any feedback? Enjoy talking about design systems? I’d love to hear from you — let’s connect on Twitter!

Illustrations by Meredith Fay unless otherwise noted. Special thanks to Kelvin Lu for helping me work through the math presented, as well as Danny Banks, Christophe Coutzoukis, Larisa Deac, Meredith Fay, Shinji Kim, and Will Newton for reading drafts of this essay.

[1] Designing for accessibility isn’t only about taking users’ visual impairments into account — it’s about catering to the needs of all people who might use a product, including those with physical, visual, auditory, or speech disabilities. Since this post is focusing on our color system, however, visual accessibility will be its focus.

[2] The main alternative to this “descriptive” naming system is one that uses “semantic” names. In these systems the color names are named based on their intended meaning (primary, warning, background, etc.) rather than their hue (blue , teal , gray , etc). Bootstrap is a popular framework that uses this type of naming system.

[3] You might have noticed that we’re now using larger numbers to represent brighter colors, whereas most other systems (and our original system) do the opposite. We made this choice to drive home the point that the number is conveying precise information about luminance, and wanted an increasing number to correspond with increasing luminance.

--

--

Code + design systems. Working at @Amplitude_HQ. Enjoys audiobooks, coffee, skiing, running, and Liverpool FC. He/him.