We think all accessibility stories are interesting and unique, including our own! In this content series we’ll share how we’ve landed at our own guiding principles and what we’ve discovered about building and testing an accessible product for our customers. We hope it inspires you to share yours, ask questions and start conversation about accessibility standards for the digital experiences we all share.

This post describes how we at Lookback

  • developed a new color system for our web and mobile apps.
  • incorporated accessibility considerations on colors from the WCAG into our workflow.

Our technical workflow for accessible colors

Last Spring, we set out to create a better color system for Lookback’s software products. The initiative came from both technical and accessibility standpoints.

In our web products, we’ve never really had a color system in the traditional sense of the word: canonical, centralized, and flexible. Colors have been added or best case reused from design mockups as we’ve progressed with the product. At least, colors lived as variables in the CSS preprocessor SCSS, but in a ton of locations where color values were modified on the fly with “adjuster functions” such as darken, lighten, and so on, to get slightly different hues and shades. Long story short: it became increasingly difficult to manage the sprawl of colors in the frontend code.

Accessibility wise, it didn’t require any fancy external consultants to make us realize we didn’t have proper contrast levels in our products. The Chrome DevTools has a quick and dirty way of inspecting contrast levels for elements in a web page:

We ended up having bad contrast almost everywhere. Fixing this would require not just spot fixes, but a systematic overhaul of the color system. This neatly tied into the tech goals described above. Thus, we set out to fix this!

More on contrast level checking further below.

Creating a system

We use Figma for all our design creation and collaboration. Best of all: Figma supports shared component and color libraries. This means you can define a set of named colors and share those with your collaborators across files. Thanks to that, updating the link colors across all files is merely becoming a single update in the library. This is not a new idea, but an immensely powerful one.

We started out by reading a ton about previous work in this area. There’s a lot of theory behind color systems from a scientific perspective, but we opted for a balance in our system’s “correctness”.

Starting off, the post “Building Your Color Palette” from Refactoring UI helped us get off the ground in our thinking. We followed the flow:

  1. Find accent colors.
  2. For each accent color, pick a base color.
  3. For each base color, find the edges.
  4. Fill in the gaps in between base and edges.

For each color in steps 2-4, we made sure to check the contrast levels against a dark or light foreground color.

Here’s a system we landed in:

You see the base colors in the center row, named xxxx-50. Edge colors are xxx-10 and xxxx-100.

The process of picking colors

Step 3 above felt a bit daunting to us. How would we know we had picked the “correct” shade? And how to pick out the base colors to base the whole scale on?

“Designing Systematic Colors” by UX Planet provided a lot of inspiration for a methodical approach, and other small gotchas in terms of accessibility, color scales, and the holistic system. A recommended read.

Our process was less scientific that the one outlined in UX Planet’s post. Since a color system works like a matrix, the scales need to match in both the horizontal and vertical axis. It’s easy to get paralyzed by the sheer amount of paths to take here, but we bluntly started out with one of our accent colors: blue.

Some takeaways in this stage:

  • The 100 color can be almost black but with a tint of the accent color. Similarly, the 10 color can be almost white but with a tint of color.
  • The colors in the upper range should have a lot more saturation than the ones in the lower parts.
  • Balance the need for subtle change for each step (like when you need a slightly different color for a hover effect) and the uniformity of the whole scale. There shouldn’t be too many “uneven” jumps.
  • We opted for a slight tint of blue in our greyscale, simply because it looked better – plain grey is usually quite boring.

After picking out the scale of 10 to 100 with input from UX Planet, we created a “testbed” for the scale. This was to simulate real world interface elements:

You notice we built for dark mode interfaces from the start here, something we recommend doing. All colors in the screenshot above are from the single blue scale. In light mode, the button uses blue-60 as base, and in dark mode it’s blue-40. In this testbed, we tweaked the shades and hues even further to make the elements have a harmonic feel. We repeated this process for all colors:

By switching between the testbeds and the color matrix, doing tweaks, we could identify rough edges in the scales, and finally a complete color system popped out.

Things to note in this process:

  • It’s about coordinating shades across all colors in the system. Switching a button’s color from blue-60 to green-60 shouldn’t produce a dramatic shift in the button’s color shade. That’s why it helps eyeing the horizontal rows in the color system, while also comparing across testbeds.
  • In each step, you must ensure that the contrast levels are fine. More on that below.
  • In our color system above, you see we switch to white foreground text on the color swatches from 60 and above. We simply disallow dark foregrounds against backgrounds of 60-100 in our system, and vice versa with light foregrounds against backgrounds 10-50, due to contrast levels.

Making a testbed for each color helped a lot. It’s easy to get “lost in the system” when just staring at a grid of colors. Using them in real life is another thing. Actually seeing how they match each other in the interface is crucial. Also, it doesn’t stop here: you mustn’t be afraid of going back to the system and do tweaks if it turns out some colors don’t work out in the final products.

Due to the interactivity of user interfaces, more parameters come into play as well. States on a component, like hovering a button, could be taken into consideration when designing the color system. For us, we decided early on to not be that strict when it comes to those states. For buttons, for instance, we opted for modifying colors on the fly when a button is hovered, pressed, disabled, and so on. The difficulty came when we wanted subtle change in color for a state. Or color scales (10 steps) simply wasn’t fine enough to express finer states. This is something to think about when designing.

Contrast levels and accessibility

We’ve mentioned contrast a few times so far. One of the initial reasons for revamping our color system was due to lack of strong contrast in the user interface: for links, buttons, and copy.

Why is enough contrast important? Because people with low vision, color blindness, or other visual impairments won’t be able to read or use your interface if there’s low contrast. There are a lot of tools for testing your interface with various kinds of visual impairments, which is a really eye opening experience.

A lot of smart people have thought about all of this, and have collected a ton of material on accessibility as the Web Content Accessibility Guidelines (WCAG). The latest set of guidelines is even an ISO standard.

Low color contrast on the web is a widespread issue, but alas so easily fixable. The organization WebAIM publishes an annual report on the accessibility of the top million web pages, named “The WebAIM Million”. The February 2020 numbers are quite depressing, to be blunt. 86.3% of the web pages analyzed failed the WCAG color contrast recommendations for text. Low contrast was the most common failure type. This is clearly a low hanging fruit to fix.

WCAG defines a set of criterias for color contrast, named Criterion 1.4.3 and Criterion 1.4.6. These two describe the levels of contrast which makes the color pass the guidelines. These are:

  • Level AA: a contrast ratio of 4.5:1 for regular text, and 3:1 for large text.
  • Level AAA: a contrast ratio of 7:1 for regular text, and 4.5:1 for large text.

As you can see, Level AAA is stricter. The article “Contrast and Color Accessibility” by WebAIM does a good job of explaining these concepts in a hands-on way. Also, “Color Contrast, Beyond the Basics” by 24 Accessibility dives deeper in understanding the WCAG color guidelines for common interface elements such as checkboxes, navigation menus, and maps.

At Lookback, we have set the baseline at AA compliance, but aim for AAA.

Back to the process. For each color in our system, we checked that it had at least AA level of contrast. WebAIM has a great tool for it.

Example: with the color grey-60, we put the color code into a contrast checker tool as the background, and used a white foreground. With the color blue-50, we used a black foreground color in the contrast checker tool. This cut off point was arbitrarily put in the middle of the scale. This means that any colors below the -60 scale must be paired with a dark enough foreground or background. A color above -50 must have a light background or foreground.

It was a bit annoying to get a fail on the contrast for a specific color we just had settled on. When that happened, we simply had to pick the closest AA compliant color. It might feel weird if you’ve designed interfaces for a decade, and now you’re forced to not pick the one true color you had in mind. But in the end, this is for the greater good. In almost all cases, we just needed to do small bumps to make the color in question a bit darker.

Here are some neat color contrast tools:

Checking the contrast of a button’s text color against its background in the Lookback dashboard with the Color Contrast Analyzer app.

Note that we had to make sure to manually check interface elements for contrast failures as we implemented designs as code. Google Chrome’s DevTools has an inline color contrast checker when you inspect an element too:

Here you see that the inspected text fulfills the AA guidelines with the contrast level of 11.12:1.

Design and development process

With the color system finished (if it can ever be “finished”?), we could jump to the next step: getting the colors into the product code! Lookback maintains apps for the web, iOS, and Android. Sharing colors between these platforms would be the ideal solution: update a color once, and see the results everywhere without having to manually update the color codes in the source code.

Our colors lived in Figma, and luckily, they provide a web API! Upon inspection, we realized we could programmatically fetch the raw color codes out of the Figma document and put them into the source code as CSS or JSON. We created a GitHub repository to hold this script, CSS source code, and the colors in Javascript and JSON formats. You’ll find links to source code at the bottom of this post.

Automated tests for contrast violations

To ensure we’d catch any WCAG color violations before code is deployed for customers, we set up an automated test suite in the repository which would check all colors for violations. We used a tool called Pa11y for this. In our tests, Pa11y will be given a generated HTML page with all colors in our system, and fail the suite if some colors have bad contrast. This step provides an extra safe guard if we’d miss checking contrasts when picking colors in Figma.

Distributing the colors

Now when we had a solid color palette and a way to get those colors down into code, we needed a way to get them out in actual apps.

We started with CSS.

After running the Figma fetch script, we had all the colors as a big dictionary looking like this:

'blue-10': '#f6fbfd',
'blue-20': '#e5f7ff',
'blue-30': '#a4e3ff',
'blue-40': '#60ceff',

There are a bunch of ways to use these in CSS. Some technical readers might’ve used LESS or SCSS. In recent years, CSS Custom Properties have gained popularity and become adopted by more browsers. That means we could just distribute the color variables in a .css file and they could be used anywhere in the CSS.

We ended up with all the colors as custom properties in the CSS like this:

:root {
--blue-10: #f6fbfd;
--blue-20: #e5f7ff;
--blue-30: #a4e3ff;
--blue-40: #60ceff;

/* ... */

Again, we wrote a script which takes all the colors from the large dictionary above and converts them to CSS Custom Properties and inserts them into the final distributable .css file. Distributing to CSS done!

Distributing to JSON was easy: we just stringified the dictionary of colors and put it into a .json file. With .json and .css files ready to go, we … drumroll … wrote another script for putting the distributable files online in an Amazon S3 bucket. Storing them there meant the iOS and Android codebases could do a HTTP request and fetch the .json file with all colors, and include in their respective source code.

Any web app could just embed the CSS and use the colors:

<link rel=”stylesheet” href=”https://cdn.foo.com/latest/lookbook.dist.css

body {
color: var(--blue-80);

No more magic, random color values in the CSS. Just safe, accessible color variables.


Summarized, all this becomes quite a nice pipeline for working with colors:

  1. Designer does adjustments to the shared color library in Figma.
  2. Developer runs the fetch script to update the colors in the repository.
  3. After running the automated accessibility tests, the developer issues a new release which puts the CSS and JSON files in the Amazon S3 bucket.
  4. Web clients get the CSS with the new colors. iOS and Android clients run their fetch scripts to get new colors from the JSON file.
  5. New colors are rolled out in each platform’s release process.

Testing accessibility can be a detailed, manual process. But at least with colors, it can be made automated.

Improvements to this setup would be to have semantic color variable names. For instance, we’ve used the color swatch “blue-60” for all links in our web apps, and the same for the background colors on all buttons across platforms. Our color repository could provide these definitions centrally, like this:

// colors.json
"primaryButtonBg": "blue-60",
"linkColor": "blue-60",

This could help unify the platforms so they wouldn’t have their own definitions.

Feel free to inspect the colors repository on GitHub to see the structure.