DEV Community

Louis Liu
Louis Liu

Posted on

How to Choose the Font Color Based on the Background

In the web browser, the font color is set to black by default. Have you ever thought about why? It's not rocket science, just because of the background and the text has high contrast so you can tell apart the text and the background.

When the background is white, you use the black font and when it's black you choose the opposite. But what if the background is not black and white but other colors? (Suppose we only have the black and white font in this world 😝😝)

The text on these buttons looks harmonious, right? (I take them from the Bootstrap document)

Image description

If I reverse the font color:

Image description

You still can see the text on most of the buttons but the contrast becomes lower.

Here's another example. On the dark blue background, the white font is easier to read. However, the black font is a better choice on the water blue background.
Image description

As a software engineer, I always implement the UI based on the design, I barely think about how the designer chooses the font color until a question comes up in my mind. What if I want to display a text on the background that will change its color dynamically? Like, on Monday it's white but on Tuesday it changes to blue.

How do you choose the font color?

We can choose font color based on the background luminance. According to the contrast ratio formula given by W3C Recommendation:

contrast ratio = (L1 + 0.05) / (L2 + 0.05)
Enter fullscreen mode Exit fullscreen mode
  • L1 is the relative luminance of the lighter of the colors, and
  • L2 is the relative luminance of the darker of the colors
  • Lblack is 0
  • Lwhite is 1

I will use L to represent the luminance of the background color. Now, we can say if (L + 0.05) / (Lblack + 0.05) > (Lwhite + 0.05) / (L + 0.05) we can use black(#000000) as the font color. Otherwise, use white(#ffffff).

Let's simplify the formula:

(L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05) 

(L + 0.05)2 > (1.05 * 0.05)

(L + 0.05) > √(1.05 * 0.05)

L > √(1.05 * 0.05) - 0.05

L > 0.179

We only need to calculate the L. The formula to calculate the relative luminance is given by W3C Recommendation.

// suppose background color in hexadecimal formart: #ff00ee
function textColorBasedOnBackground(backgroundColor) {
  backgroundColor = backgroundColor.substring(1);
  const r = parseInt(backgroundColor.substring(0,2), 16); // 0 ~ 255
  const g = parseInt(backgroundColor.substring(2,4), 16);
  const b = parseInt(backgroundColor.substring(4,6), 16);

  const srgb = [r / 255, g / 255, b / 255];
  const x = srgb.map((i) => {
    if (i <= 0.04045) {
      return i / 12.92;
    } else {
      return Math.pow((i + 0.055) / 1.055, 2.4);
    }
  });

  const L = 0.2126 * x[0] + 0.7152 * x[1] + 0.0722 * x[2];
  return L > 0.179 ? "#000" : "#fff";
}
Enter fullscreen mode Exit fullscreen mode

Example

Here's an example. You can observe the font color while changing the background color.

References:
Web Content Accessibility Guidelines (WCAG) 2.1
Determine the best text color for a given background color
How to decide font color in white or black depending on background color?

Top comments (5)

Collapse
 
fabiogiolito profile image
Fabio Giolito • Edited

Soon we'll be able to do this in CSS with color-contrast.

.text-contrast {
  color: color-contrast(var(--bg-color) vs white, black);
}
Enter fullscreen mode Exit fullscreen mode

You can add any number of colors and it will pick the one with better contrast automatically.

developer.mozilla.org/en-US/docs/W...

Collapse
 
louis7 profile image
Louis Liu • Edited

@fabiogiolito thanks for sharing this! This function is cool, it can compare colors other than black and white.

Collapse
 
lionelrowe profile image
lionel-rowe

Omg that's incredible news! Hope implementations start cropping up soon.

Collapse
 
lionelrowe profile image
lionel-rowe • Edited

Nice demo! Here's a version that I think is a bit more self-documenting and also rejects invalid inputs:

const HEX_COLOR_MATCHER = /^#(?<r>\p{AHex}{2})(?<g>\p{AHex}{2})(?<b>\p{AHex}{2})$/u

function relativeLuminance(srgb) {
    const [R, G, B] = srgb.map((i) => i <= 0.04045 ? i / 12.92 : ((i + 0.055) / 1.055) ** 2.4)
    return 0.2126 * R + 0.7152 * G + 0.0722 * B
}

function hexToSrgb(hex) {
    const match = hex.match(HEX_COLOR_MATCHER)
    if (!match) throw new Error('Not a valid 6-digit hex color')
    const { r, g, b } = match.groups

    return [r, g, b].map((x) => parseInt(x, 16) / 255)
}

function contrastingColor(hex) {
    return relativeLuminance(hexToSrgb(hex)) > 0.179 ? '#000000' : '#ffffff'
}
Enter fullscreen mode Exit fullscreen mode

\p{AHex} in a Unicode-aware regex matches any ASCII hex digit.

Collapse
 
louis7 profile image
Louis Liu

@lionelrowe I was going to create a gist to provide a comprehensive function. Yours is awesome!