Reverse Text Color Based on Background Color Automatically in CSS

Avatar of Robin Rendle
Robin Rendle on (Updated on )

Over the weekend I noticed an interesting design for a progress meter in a videogame. The % complete was listed in text in the middle of the bar and didn’t move. But that text was the same color as the background of the bar that was filling up from left to right. It seemed like the background was going to make the text invisible once they overlapped, but instead, the text color reversed to be white instead anywhere it overlapped with the background.

My first thought was this: how can we replicate this design pattern and what might we learn along the way?

Here’s what I came up with, but make sure to check this demo on the latest version of Chrome to see everything working correctly:

See the Pen A pure CSS loading bar by Robin Rendle (@robinrendle) on CodePen.

Pretty cool, huh? This is possible with the awesome magic of mix-blend-mode in CSS.

Let’s take a look at the markup first

<div class="wrapper">
  <div class="bg">
    <div class="text"></div>
  </div>
</div>

The .wrapper will hold our elements in place, .bg will be our loading bar that increases over time and our .text element will be used as the percentage information.

Let’s make the whole thing CSS-only

A “real” loader on the web would likely be powered by JavaScript and reacting to actual data somehow. But while we’re having fun here, let’s make the whole thing, even the counting, happen just in CSS (SCSS for the looping help).

We’ll set up our variables and style the .bg element:

$loadingTime: 10s;
$color: rgb(255, 0, 0);

.bg {
  background-color: $color;
  animation: loading $loadingTime linear infinite;
}

With the loading animation we’ll be changing the width of the element, like so:

@keyframes loading {
  0% {
    width: 0;
  } 100% {
    width: 100%;
  }
}

Perhaps we could have hidden the overflow and moved the background box with transform property instead (for performance reasons) but in this little demo I think it’s fine to animate the width property alone.

To update the content of the .text element with the correct percentage value we have to be a little dastardly and use a mix of pseudo elements and animations. First we’ll keep the <div> empty set the content of its after pseudo element to 0% before defining an animation:

.text {
  color: $color;
  width: 200px;
  border: 1px solid $color;

  &:after {
    padding-left: 20px;
    content: "0%";
    display: block;
    text-align: center;
    animation: percentage $loadingTime linear infinite;
  }
}

So what we want to do with the percentage animation above is update our content property with each value from 1 to 100, like this:

@keyframes percentage {
  0% {
    content: "0%";
  }
  1% {
    content: "1%"; 
  }
  /* ... */
  100% {
    content: "100%";
  }
}

But instead of making all those @keyframe selectors by hand we can familiarise ourselves with the @for loop syntax in Sass:

@keyframes percentage { 
  @for $i from 1 through 100 {
    $value: $i + "%";
    #{$value} {
      content: $value;
    }
  }
}

If this looks a little scary then not to worry! On the third line we add whichever number is currently in the loop (which we call $i) and make that a string by appending % to it and assigning it to a variable. Then we can use interpolation to make each @keyframe selector update the content property to the right value.

The Color Swap

Finally, all we have to do is set the color and the mix-blend-mode of our pseudo element and there we go; a pure CSS loader where the background color influences the foreground text:

.text:after {
    /* This value is the OPPOSITE color of our background */
    color: rgb(0, 255, 255); 
    mix-blend-mode: difference;
}

See the Pen A pure CSS loading bar by Robin Rendle (@robinrendle) on CodePen.

With the difference blend mode we have to set the text element’s color value to the opposite of the background. So if our background is rgb(0, 0, 0) we’ll need to set the text pseudo element to rgb(255, 255, 255).

I think this little demo helps us recognise how useful the mix-blend-mode property can be. There are going to be all sorts of instances like this in the future where interfaces can reveal information in ways we’d never have thought possible before.

Changing Text Color Entirely

The cool part of this technique is the fact that some of the text is one color and other parts of the text is another color. The reversing happens just based on what is covered and what isn’t, even if it’s just a part of a letter.

If you were looking for more of an accessibility-based “change the text color to make sure it has enough contrast” thing, Sass can also help with that.

Another Example

The XOXO site used mix-blend-mode: darken; quite a bit to to have backgrounds, shapes, and text all interact in subtle/interesting/beautiful ways that we haven’t seen a whole lot on the web yet.

Browser support

The mix-blend-mode property isn’t well supported at the moment and neither is the animatable content property. So make sure to provide fallbacks if you decide to use either of these tricks.