Rose Tinted Classes: An SVG Filter Function CSS Trick


In this post, we'll use CSS and SVG filter functions to tint an element's content Rose Quartz, Pantone's 2016 Color of the Year. The exact hex code for Rose Quartz is probably a trade secret, so I'll be using #F7CAC9 and praying I don't get sued by Big Color.

Here's the content we're trying to rose tint:

The dragon is one of the 12 animals in the Chinese zodiac. There are more people born in Dragon years than in any other animal years of the zodiac.

The Old Solution

Alas, there's no CSS filter function that colorizes content. For many years, the recommended hack was to apply the sepia() filter, then hue-rotate() your way to the target tint color. The sepia() filter did double duty in this approach. It desaturated the content to grayscale, then applied a common tint color to everything.

If traveling back to the 1800s to introduce color seems bonkers, you're not alone. That part was strange, but the hard part was figuring out how to rotate sepia to your target hue, then adjusting brightness and saturation to match the target. People even wrote calculators for this.

Unfortunately, the whole sepia rotation approach is doomed. You can't precisely reach all target colors this way. For some target colors, things will always look a little bit off.

There must be a better way!

SVG Filter Elements

Enter <feColorMatrix>. This SVG filter element lets you define an (R,G,B,A) -> (R',G',B',A') color transform in matrix multiplication terms. Armed with this, you can finally do precise colorization. This better approach works like this:

  1. First, desaturate the content via the grayscale() filter.
  2. Next, turn the grayscale result into an alpha mask.
  3. Use <feColorMatrix> to fill the alpha mask with the target color.

There's one more wrinkle though. If the element you're tinting contains images with translucent pixels – say pngs or webps – you need to perform alpha compositing so the tinted pixels continue to look translucent.

In our case, alpha compositing just means "take a pixel's color and multiply it by its 0-to-1 opacity level." Because we're going to apply a grayscale() filter first, which makes R = G = B, the alpha compositing step only needs to put R*A into the A channel of the alpha mask.

Matrix multiplications are linear operators on (R,G,B,A) vectors, so you can't use an <feColorMatrix> to work your way to (0,0,0,R*A). For that we're going to need the <feComposite> SVG filter element, which can pointwise multiply (0,0,0,R) vectors by (0,0,0,A) vectors.

The New Solution

Putting it all together, here's an SVG that defines two filters we can use via CSS. One to create a composited alpha mask, and one to colorize with the tint color.

<svg id="tint" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="tint-alpha-mask" color-interpolation-filters="sRGB">
      <!-- Map the grayscale image pixels (R,_,_,A) to (_,_,_,R*A),
           ie do alpha compositing to create an alpha mask. -->
      <feColorMatrix in="SourceGraphic" result="r2a" type="matrix"
        values="
          0 0 0 0 0
          0 0 0 0 0
          0 0 0 0 0
          1 0 0 0 0" />
      <feColorMatrix in="SourceGraphic" result="a2a" type="matrix"
        values="
          0 0 0 0 0
          0 0 0 0 0
          0 0 0 0 0
          0 0 0 1 0" />
      <feComposite in="r2a" in2="a2a" operator="arithmetic" k1="1" k2="0" k3="0" k4="0" />
    </filter>
    <filter id="tint-colorize" color-interpolation-filters="sRGB">
      <feColorMatrix type="matrix" values="0 0 0 0 0.969
                                           0 0 0 0 0.792
                                           0 0 0 0 0.788
                                           0 0 0 1 0" />
    </filter>
  </defs>
</svg>

Note how our Rose Quartz tint color – #F7CAC9 = (0.969,0.792,0.788) – appears in the fifth column of the feColorMatrix. Change that to whatever tint color you're targeting.

Using these SVG filters from CSS is easy, you just chain them:

.rose {
  filter: grayscale(1) url(#tint-alpha-mask) url(#tint-colorize);
}
svg#tint {
  display: none;
}

And here's the result:

The dragon is one of the 12 animals in the Chinese zodiac. There are more people born in Dragon years than in any other animal years of the zodiac.


Posted by Alan on Tuesday, August 12, 2025.