Make a Text Match Any Background Color
Tags: webdev, ux, a11y
Published: 2020-07-26 20:21:02

---
title: Make a Text Match Any Background Color
published: true
description: Learn about contrast ratio, luminance, and how to use these to make a text color match any background in an accessible way.
tags: WebDevelopment, UX, Accessibility
cover_image: https://res.cloudinary.com/dmq9pzw0o/image/upload/v1595794727/how-to-make-a-text-color-fit-any-background-color/Group_2_4_qmcdrr.png
canonical_url: https://jfelix.info/blog/how-to-make-a-text-color-fit-any-background-color
---

Have you ever needed to use text inside a card that has generated colors? Give the user the capacity to choose their own color for a component that contains text like a navbar? These are situations where texts need to adapt to the current background color. Making each color match the text would be tedious and unscalable. By using the luminance and contrast ratio, we can determine if the text should be dark or bright while guaranteeing an accessible contrast.

## Contrast Ratio

The goal of text with a background is that it can be easily read by the users, including those with moderately low vision. To understand when a text is legible or not, first, we have to understand what contrast ratio is. Contrast ratio is a property that is defined by the luminance (amount of light) of the brightest color (white) to that of the darkest color (black) that a system is capable of producing. According to the W3C, the formula is `(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.

The luminance scale can go from 0.0-1.0. Black being 0.0 and white 1.0. You may notice that if we calculate the ratio with the extremes of the scale's interval  `(1.0 + 0.05) / (0.0 + 0.05)` we would get 21, and if both are 0.0 then we get 1. This means that the ratios will range from 1 to 21. These are commonly written as 1:1 to 21:1.

The recommended contrast ratio between a text and a background should be at least **4.5:1**. 

## Determining Text Color Dynamically

Now that we know what contrast ratio we should aspire to. It is time to find a formula so that we can determine which color to use. 

According to Mark Ransom, to determine if we need a darker or brighter color, we just need to compare the contrast of background color with white and black. If the contrast for black is higher then we use black, otherwise, we use white. We can represent this in Javascript the following way:

```jsx
if ((L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05)){
	use #000000
} else {
  use #ffffff
}
```

If we simplify it: 

```jsx
if (L > Math.sqrt(1.05 * 0.05) - 0.05){
	use #000000
} else {
  use #ffffff
}
```

Now, All that is left is calculating the luminance!

## Calculating the Luminance

Getting the luminance is pretty straightforward if you have an RGB color. According to the W3C, the formula for luminance is `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

In Javascript this would translate to: 

```jsx
/*
	If color has the following format: 
	const rgbColor = {
		r: 100,
		g: 100, 
		b: 100
	}
*/

// Extracted from Polished
// Code is licensed with an MIT license
function getLuminance(rgbColor){
 const [r, g, b] = Object.keys(rgbColor).map(key => {
    // Our color numbers represent a 8bit channel. 
		// The formula requires a sRGB channel which is defined by 
		// ColorChannelIn8bit / 255
    const channel = rgbColor[key] / 255
    return channel <= 0.03928
      ? channel / 12.92
      : ((channel + 0.055) / 1.055) ** 2.4
  })
  return parseFloat((0.2126 * r + 0.7152 * g + 0.0722 * b).toFixed(3))
} 
```

Frequently, we don't have our colors in RGB, but in hex or color name. That is why I recommend checking out [polished](https://github.com/styled-components/polished) library which exposes a `getLuminance` function that returns the luminance given a hex or color name.

## Bringing It All Together

Now that we know how to determine the color's value using its luminance and comparing contrast ratios, we are ready to apply it to an app. For the sake of this post, I will display how to do this with do a quick possible scenario. We have a card in which the user can change the background color with a color picker. Our job is to select a text color that is accessible and visible to all our users. 

Here is a sandbox with the finished product. Feel free to tinker with it!

{% codesandbox dynamic-text-change-with-color-x903b %}

## Conclusion

Adjusting the text color to match a background is very common when developing dynamic UIs. A contrast ratio of 4.5:1 is essential to guarantee an accessible contrast between a text and a background. By comparing the contrast ratios of both white and black using the background color's luminance, it is possible to efficiently determine the text color used with a background.  

For more up-to-date web development content, follow me on [Twitter](https://twitter.com/Jose_R_Felix), and [Dev.to](https://dev.to/jfelx)! Thanks for reading! 😎

## References

- [https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color](https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color)
- [https://www.w3.org/TR/WCAG20/](https://www.w3.org/TR/WCAG20/)

---

Did you know I have a newsletter? 📬

If you want to get notified when I publish new blog posts and receive an **awesome weekly resource** to stay ahead in web development, head over to [https://jfelix.info/newsletter](https://jfelix.info/newsletter).