Color Theory: Accessible Complementary Colors

Cole Turner
Cole Turner
3 min read
Cole Turner
Color Theory: Accessible Complementary Colors
Blog
 
LIKE
 
LOVE
 
WOW
 
LOL

Maybe it's the software engineer in me, but I love solving complex problems in lazy ways. If there is a way that I can script it, program it, or otherwise automate - I will find it.

In my last post, I shared how you can use Google Chrome's Color Picker to Improve Text Color Contrast. This post was largely influenced by the work I've done on my personal site to create dynamic color schemes from a selection of key colors.

Here is an example:

Text with two complementary colors (green and purple)

This example shows two colors: green and purple. These colors are complementary to each other, and it shows. Look at how stunning they are together.

But I didn't choose that purple color.

I did choose that green color. Using color theory, the purple color is generated as an RGB complementary color. A complementary color is generally found opposite on the color wheel:

Color Wheel - With Purple and Green Complimentary Colors

Complementary colors feel vibrant and can make things on the page pop. Luckily, there are a few tools out there that already exist, and can help us to do that.

The invert-color package can take an input color and generate a complementary color. Let's give it a whirl. Using your favorite package manager, install this package and grab your favorite color.

import invertColor from 'invert-color';

const color = "#04c9db"; // (cyan)
const complementaryColor = invertColor(color);

// complementaryColor = #fb3624 (red)

And just like that, we have our complementary color. We can use this color in subheadings, quotes, or wherever it fits. But what if we need it to render in both dark and light modes? What about the generated color contrast?

We want our complimentary color to pop. More importantly, we want it to be legible. In some cases, the complementary color needs some adjustment for it to be accessible by a wider range of audiences. If we look at our color without any adjustment, it looks like this:

Text with complementary colors, switching from dark to light mode

This is a good start. However, when checking the text color contrast, it deserves more contrast to become more accessible. Could we software engineer this problem?

You betcha.

The YIQ color space is useful here because unlike RGB, it produces better results for calculating color contrast. For our use case, we're going to calculate the luminance value of our complementary color, and then adjust to 128 (halfway between black and white).

To improve the hex color contrast, we will:

  1. Convert our color to RGB.
  2. Determine the luminance value.
  3. Calculate the percent change from the luminance value to 128 (50%).
  4. Adjust the color brightness of the original color, using the percent change.

These problems have been solved before, so rather than try to solve it myself, let's use the fine work from open source.

To convert your hex color to RGB, you can use the hex-rgb package. Convert the r/g/b values to the luminance value (YIQ) using the getContrastYIQ function. Once you've done that, we will calculate the percent change and then change the color's brightness using this function to lighten/darken hex colors.

Here's how you do that:

// Invert the initial color 
const complimentaryColor = invertColor(color);

// Convert the hex code to RGB
const { r, g, b } = hexToRgb(complimentaryColor);

// Determine the luminance value of the R/G/B color
const yiq = rgbToYIQ({ r, g, b });

// Detect if using dark mode
const match = window.matchMedia('(prefers-color-scheme: dark)');

// Mid-way between black (#000) and white (#fff)
// You can change this for dark mode or light mode too.
const threshold = 128;

// Calculate the brightness percent change with color scheme in mind.
// -> Lighter for dark color scheme
// -> Darker for light color scheme
const brightnessChange =
    match.matches
      ? Math.max(yiq, threshold) - Math.min(yiq, threshold)
      : Math.min(yiq, threshold) - Math.max(yiq, threshold);

After making these changes, the complimentary will now have improved contrast. Here's an example where it made an improvement from Level AA to Level AAA:

Side-by-side comparison of complementary color with contrast adjustment. The example on the left does not meet AAA requirements. The example on the right does.

You can hardly tell the difference, so let's view the same change in dark mode:

Before and after comparison of improved text color contrast in dark mode.

With this small change, our text meets Level AAA requirements to have accessible text color contrast. And all without making any sacrifices to the design.

How awesome is that?

 
LIKE
 
LOVE
 
WOW
 
LOL

More Stories

Why I Don't Like Take-Home Challenges

4 min read

If you want to work in tech, there's a chance you will encounter a take-home challenge at some point in your career. A take-home challenge is a project that you will build in your free time. In this post, I explain why I don't like take-home challenges.

Standing Out LOUDER in the Technical Interview

5 min read

If you want to stand out in the technical interview, you need to demonstrate not only your technical skills but also your communication and collaboration skills. You need to think LOUDER.

See more posts

Read it before anyone else. Subscribe to my newsletter for early access to the latest news in software engineering, web development, and more.