CSS Difference Blend Mode

Animated mix-blend-mode: difference: a sweeping bar inverts text while orbiting circles subtract color channels.

Level
Advanced
Published
Updated
Reading time
8 min

mix-blend-mode: difference subtracts the element's color from whatever is behind it, channel by channel, and keeps the absolute value. That single rule produces the inversion effect below, where a white bar stays white over the dark stage and turns the pale letters black the moment it crosses them. No masks, no duplicated text, no script.

Two things have to be right for it to behave. The blend needs a boundary, which isolation: isolate provides, and the colors underneath have to be known, which is why this page pins its stage colors instead of taking them from the theme. Both are covered below, along with a comparison of four blend modes over the same backdrop and a version that blends an element's own background layers rather than the page behind it.

A bar that inverts what it crosses

DIFFERENCE

// a white bar sweeps across: where it meets the letters, both go black
// overlapping circles subtract each other's color channels

HTML

<div class="blend-stage">
  <p class="blend-word">DIFFERENCE</p>
  <div class="blend-bar"></div>
</div>

<div class="blend-orbit">
  <span class="orb orb-red"></span>
  <span class="orb orb-green"></span>
  <span class="orb orb-blue"></span>
</div>

CSS

/* isolation makes the stage its own stacking context, so the blending
   stops here instead of reaching the page behind it. */
.blend-stage {
  position: relative;
  width: 100%;
  height: 150px;
  overflow: hidden;
  border-radius: 12px;
  background: #0c0c0d;
  display: flex;
  align-items: center;
  justify-content: center;
  isolation: isolate;
}

.blend-word {
  margin: 0;
  font-size: 2.6rem;
  font-weight: 800;
  letter-spacing: -.02em;
  color: #f0f0f0;
  user-select: none;
}

/* white over near-black stays white; white over the near-white
   letters lands on black, which is what reads as an inversion. */
.blend-bar {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 22%;
  background: #ffffff;
  mix-blend-mode: difference;
  animation: blend-sweep 4s ease-in-out infinite;
}

/* animating left keeps the sweep proportional to the stage,
   so the same rule works at any width. */
@keyframes blend-sweep {
  0%   { left: -25%; }
  50%  { left: 100%; }
  100% { left: -25%; }
}

.blend-orbit {
  position: relative;
  width: 100%;
  height: 150px;
  overflow: hidden;
  border-radius: 12px;
  background: #0c0c0d;
  isolation: isolate;
}

/* the negative margins center each circle, so the animation
   only has to carry the orbit. */
.orb {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 104px;
  height: 104px;
  margin: -52px 0 0 -52px;
  border-radius: 50%;
  mix-blend-mode: difference;
  animation: blend-orbit 7s linear infinite;
}

.orb-red   { background: #ff3b30; }
.orb-green { background: #34ff7a; animation-delay: -2.33s; }
.orb-blue  { background: #3b6bff; animation-delay: -4.66s; }

/* rotate, then push outward: a circular path in a single transform.
   The negative delays above space the three circles evenly around it. */
@keyframes blend-orbit {
  from { transform: rotate(0deg) translateX(30px); }
  to   { transform: rotate(360deg) translateX(30px); }
}

Other ways to build it

The same bar, four modes

Identical markup, identical colors, one property changed. The bar sits still in each panel so the four results can be compared at the same overlap. difference and exclusion both invert, and exclusion is the softer of the two because its formula flattens toward mid gray where the two colors are close, where difference drives them to black. screen can only lighten and white is its ceiling, so the bar paints solid and swallows the letters it crosses. multiply can only darken and white is its identity, so a white bar changes absolutely nothing.

BLEND

difference

BLEND

exclusion

BLEND

screen

BLEND

multiply

HTML

<div class="blend-modes">
  <figure class="blend-mode-cell">
    <div class="blend-stage blend-stage-sm">
      <p class="blend-word blend-word-sm">BLEND</p>
      <div class="blend-bar blend-bar-static blend-m-difference"></div>
    </div>
    <figcaption>difference</figcaption>
  </figure>
  <figure class="blend-mode-cell">
    <div class="blend-stage blend-stage-sm">
      <p class="blend-word blend-word-sm">BLEND</p>
      <div class="blend-bar blend-bar-static blend-m-exclusion"></div>
    </div>
    <figcaption>exclusion</figcaption>
  </figure>
  <figure class="blend-mode-cell">
    <div class="blend-stage blend-stage-sm">
      <p class="blend-word blend-word-sm">BLEND</p>
      <div class="blend-bar blend-bar-static blend-m-screen"></div>
    </div>
    <figcaption>screen</figcaption>
  </figure>
  <figure class="blend-mode-cell">
    <div class="blend-stage blend-stage-sm">
      <p class="blend-word blend-word-sm">BLEND</p>
      <div class="blend-bar blend-bar-static blend-m-multiply"></div>
    </div>
    <figcaption>multiply</figcaption>
  </figure>
</div>

CSS

/* one stage rule, one bar rule, and then only the mode changes */
.blend-bar-static {
  left: 26%;
  animation: none;
}

.blend-m-difference { mix-blend-mode: difference; }
.blend-m-exclusion  { mix-blend-mode: exclusion; }
.blend-m-screen     { mix-blend-mode: screen; }
.blend-m-multiply   { mix-blend-mode: multiply; }

/* every stage still needs its own boundary, or all four
   blend against the page instead of against themselves */
.blend-stage {
  isolation: isolate;
}

Blending an element with itself

background-blend-mode combines an element's own background layers rather than reaching behind it. Each panel here holds a diagonal stripe layer over a two color gradient, and the mode decides how they meet. Because the blending never leaves the element, there is no stacking context to think about and no isolation to remember, which makes it the safer choice whenever the layers you want to combine are all yours.

multiply
difference
overlay

HTML

<div class="blend-bg-row">
  <figure class="blend-mode-cell">
    <div class="blend-bg blend-bg-multiply"></div>
    <figcaption>multiply</figcaption>
  </figure>
  <figure class="blend-mode-cell">
    <div class="blend-bg blend-bg-difference"></div>
    <figcaption>difference</figcaption>
  </figure>
  <figure class="blend-mode-cell">
    <div class="blend-bg blend-bg-overlay"></div>
    <figcaption>overlay</figcaption>
  </figure>
</div>

CSS

.blend-bg {
  height: 130px;
  border-radius: 12px;
  background-image:
    repeating-linear-gradient(45deg,
      #ff3b30 0 12px, #ffd444 12px 24px),
    linear-gradient(120deg, #3b6bff, #34ff7a);
}

/* no isolation needed: this never reaches past the element */
.blend-bg-multiply   { background-blend-mode: multiply; }
.blend-bg-difference { background-blend-mode: difference; }
.blend-bg-overlay    { background-blend-mode: overlay; }

How it works

mix-blend-mode: difference subtracts each color channel of the element from the channel underneath and keeps the absolute value. White against black is 255 - 0 = 255, so the bar stays white over the stage. White against the near white letters is 255 - 240 = 15, so those letters land on black. That inversion is what reads as a spotlight passing over the type. The circles show the same arithmetic per channel, and where red overlaps green both are left high, which is a dull yellow.

The maths is per channel and nothing else. For every pixel the browser takes the backdrop value and the source value in each of red, green and blue, subtracts one from the other, and keeps the magnitude. Identical colors give zero, which is black, so anything blended over an exact copy of itself disappears. That is why the bar vanishes into the letters rather than tinting them: white over a near white glyph leaves a value of fifteen, and fifteen is essentially black.

Blending happens against the backdrop inside the current stacking context, which makes stacking the property that actually controls the effect. isolation: isolate on each stage creates a stacking context with no other side effects, so the blend can see the stage and nothing further back. Remove it and the bar starts differencing against whatever the nearest stacking context is, which on a simple page is the root, so the effect leaks across the whole document. Several other declarations create stacking contexts as a side effect, including opacity below 1, any transform, any filter, and a positioned element with a z-index, so the leak sometimes fixes itself by accident and then breaks again when one of those is removed.

The stage colors here are hard coded rather than pulled from the theme, and that is deliberate rather than an oversight. Difference operates on real channel values, so the result depends entirely on what is underneath. Under this site's light theme the page background becomes a warm off white, and a white bar differenced against it produces almost black, so the same rule would read as a dark smear instead of an inversion. Where a value has to survive a theme swap, custom properties are the tool, as in CSS dark and light mode. Where the arithmetic depends on the actual color, the value has to be pinned.

The circles use the same rule with three saturated sources over a black stage. Where none overlap, each is differenced against black, which returns the color unchanged. Where red at rgb(255, 59, 48) crosses green at rgb(52, 255, 122) the result is rgb(203, 196, 74), high in red and green and low in blue, which the eye reads as a dull yellow. The overlaps are therefore predictable rather than decorative: the technique is subtractive color mixing done live by the compositor.

There is a cost. An element with a blend mode has to be painted with knowledge of everything behind it in its group, so it cannot be handed to the compositor as an independent layer the way a plain transform can. That is fine for the two stages here. It is worth remembering before blending a dozen large elements on a page that also scrolls. CSS redacted text uses the other side of the same pipeline, filter, which processes an element on its own without needing the backdrop at all.

CSS properties used

mix-blend-mode
difference subtracts the element from its backdrop per channel. The value applies to the whole element including its text, borders and shadows.
isolation
isolate creates a stacking context with no other visual effect, which is what stops the blend reaching past the element you meant it to stop at.
background-blend-mode
Blends an element's own background layers with each other. It never touches the backdrop, so it needs no isolation and no stacking context at all.
overflow
hidden on the stage keeps the sweeping bar clipped to the panel as it travels past both edges.
animation
Moves the bar and the circles. Blending itself is not animated, it is simply re-evaluated on each frame at the new position.
border-radius
50% turns the three orbit boxes into circles, so the overlaps that produce the mixed colors are lens shaped.

Browser support

Feature Chrome Firefox Safari Edge
mix-blend-mode41327.179
background-blend-mode353010.179
animation455.112
transform43.53.112
border-radius433.112

Figures come from Can I Use for blending of HTML and SVG elements, background-blend-mode, CSS Animation, 2D Transforms and Border-radius. The Safari figure for mix-blend-mode needs a caveat: Can I Use marks Safari as partial rather than full from 7.1 onward and still does so on the current release, including on iOS, so test the specific mode you rely on there rather than assuming the whole set works. isolation has no separate Can I Use entry and is therefore left out of the table. In practice, any declaration that creates a stacking context does the same job, so a positioned element with z-index: 0 is a workable fallback.

Accessibility notes

Blending changes what text looks like without changing what it is, so contrast has to be checked in the blended state, not the authored one. Difference in particular can drive a pale glyph to near black over a light backdrop, and a contrast checker reading the stylesheet will report the original colors and pass. Sample the rendered pixels instead, or keep the blended treatment on decoration and leave body copy alone.

Moving inversion is uncomfortable to look at for longer than a moment, and a bar sweeping across type repeatedly is close to a flashing pattern. Stop the animation under prefers-reduced-motion and leave the bar parked, which still demonstrates the effect. Never let a blended element flash more than three times a second, since that is the seizure threshold in WCAG 2.3.1.

The blended text is a normal <p> and is read exactly as written, so nothing is lost to assistive technology. That cuts both ways: if the visual result is illegible for a sighted reader, the page still passes an automated check, because the markup is fine. user-select: none on the stage text is there to stop a stray drag selecting it, and it does not affect how the text is announced.

What you can build with it

  • Inverting headline treatments. A block of color crossing a heading, where the type flips instead of being covered, which is the demo above.
  • Cursor and scroll spotlights. A shape that inverts whatever it passes over, giving a torch effect without duplicating any content.
  • Color mixing diagrams. The three circles, used to teach subtractive mixing with real arithmetic rather than a picture of it.
  • Texture over photography. background-blend-mode combining a grain or halftone layer with an image inside a single element.
  • Duotone panels. A flat color layer blended into a grayscale background layer, giving a two tone treatment with no image editing.

Mistakes worth avoiding

  • Leaving isolation: isolate off. The blend escapes to the nearest stacking context, which is usually the page root, and the effect appears over content it was never meant to touch.
  • Theming the colors underneath a difference blend. The result is arithmetic on real channel values, so a theme swap changes the output completely and often turns an inversion into a dark smudge.
  • Assuming a blended element is composited cheaply. It has to be painted against its backdrop, so it cannot be promoted to its own layer the way a transform can.
  • Blending body text. Contrast checkers read the authored colors and pass, while the rendered pixels can be unreadable.
  • Expecting identical results in Safari. Can I Use records only partial support there, on desktop and on iOS, so a mode that works in Chrome is worth checking rather than assuming.

Frequently asked questions

How does mix-blend-mode difference work?
mix-blend-mode: difference subtracts the element's color from the backdrop color in each of the red, green and blue channels and keeps the absolute value. Identical colors cancel to black, and white over black returns white. That is why a white bar over pale text turns the text black while staying white over the dark panel.
Why is my mix-blend-mode affecting the whole page?
Because nothing between the element and the page root creates a stacking context, so the nearest blending group is the document itself. Add isolation: isolate to the wrapper you want the blend to stop at. Anything else that creates a stacking context works too, but isolation is the one with no other visual consequence.
What is the difference between mix-blend-mode and background-blend-mode?
mix-blend-mode blends an element with whatever is painted behind it in the same stacking context. background-blend-mode blends an element's own background layers with each other and never leaves the element, which means it needs no isolation and cannot leak.
Does mix-blend-mode work in Safari?
Partially. Can I Use records Safari, on desktop and on iOS, as partial rather than full support, and it still does on the current release. The common modes generally render, but the specific mode and content combination is worth checking rather than assumed.
Can I use CSS variables with a difference blend?
For the blended element, yes. For the colors underneath, be careful. Difference depends on the actual channel values behind the element, so a themed backdrop changes the result rather than recoloring it. Pin the backdrop to a fixed value when the arithmetic matters.