Published Feb 25, 2026
Updated Feb 25, 2026
By Simon

Both of the mask-image and pseudo-element CSS features are useful in their own right, but when combined, they make a great way to include icons that can change colour on hover without the need to load a second coloured asset.

I have been using this technique for a while now and am unsure where I first encountered it, but once discovered, I have never looked back.

Mask-image

A mask-image can be used on any element. At the most basic level, the opaque area of the mask image you place on top of the element is the part of the element that is retained.

Alternatively, looking at it from the other way, transparent areas of the mask image are cut away from the corresponding parts of the element.

In short, only the portions of the element that align with opaque pixels in the mask are kept; transparent pixels in the mask knock out the element underneath.

For icons, this works perfectly as the best way to add icons to a site is using an SVG, and SVG are generally shapes that are 100% alpha (in any colour), and the areas that are not part of the SVG shape are generally transparent.

The mask image in action

In the example, we are adding some social icons to the page. We want the icons to be the same colour as the text that accompanies the icon, and when we hover, we want the icon to change to the same colour as the text.

a {
  background-color: blue;
  height: 50px;
  padding: 10px;
  color: white;
  width: fit-content;
  display: flex;
  align-items: center;
  gap: 10px;
}
 
a::before {
  content: "";
  height: 25px;
  width: 25px;
  background-color: currentColor;
  mask-image: url(twitter.svg);
  mask-size: 25px;
}
 
a:hover {
  background-color: red;
  color: blue;
   
  &::before {
     /*background-color: currentColor; **Not needed**  */
     /*background-color: lightskyblue;*/
  }  
}

We have a few things of interest going on here. So let's have a closer look.

The Basics of the Code Explained

The pseudo-element a::before creates an empty box that is then shaped by the SVG via mask-image: url(twitter.svg). Think of the SVG as a stencil: its opaque (typically one colour) areas is the area that is kept and therefore is shown in the background colour of the box, while transparent areas cut everything away completely; this gives us crisp, hard-edged shapes, and the background colour of the pseudo-element's parent shows through.

By setting background-color: currentColor on the pseudo-element, the icon automatically inherits the text colour of its parent <a> (via the cascade of colour). This keeps the icon perfectly in sync with the accompanying text colour at all times. On :hover, when we change the <a>'s colour (and background-color), currentColor updates instantly, so the icon flips colour seamlessly alongside the text. No extra rules needed for the pseudo-element.

A couple of other nice bonuses in this setup:

  • The SVG file itself can stay simple/single-colour.
  • Everything remains lightweight, and you can use a vector-sharp at any size (thanks to mask-size and SVG).
  • If you want to style the icon a colour, just override the background colour of the pseudo-element.
  • It works great for theming or dark mode too: Just update the link's colour (or a custom property), and the icon follows instantly. 
    See my 2020 article on using prefers-color-scheme with CSS custom properties for a solid foundation: Using Prefers Color Scheme and CSS Custom Properties.

If you want to add a transition later, put a transition: background-color 300ms, color 300ms; on the a, and the mask/icon will smoothly follow too.

Conclusion

That is it, one rule, one SVG, and you can style icons like a king. No need to complicate things, and thus no need to complicate the conclusion.

Sign up to my newsletter below for more simple front-end development and design tips. Thanks for reading. 

Simon

Tags