Published on

Beyond border-radius: The Ultimate Guide to CSS `mask-image`

Authors

'Beyond border-radius: The Ultimate Guide to CSS mask-image'

Move beyond simple shapes and discover how the CSS mask-image property can unlock a new level of creative control for your web designs, from elegant fades to complex UI elements.

Table of Contents

Beyond border-radius: The Ultimate Guide to CSS mask-image

For years, border-radius has been our trusty sidekick for breaking out of the boxy confines of the web. Circles, ovals, rounded rectangles—it's handled them all with grace. But what if you want more? What if you need a shape that's more complex, more organic, or more... you? What if you want to fade an image into the background without opening an image editor?

Welcome to the world of CSS masking. Specifically, we're diving deep into the mask-image property, a powerful tool that lets you use the transparency or luminance of one image to selectively hide or reveal parts of another element. It's like using a stencil, but with the full power of CSS at your fingertips.

In this guide, we'll explore everything from the fundamentals to advanced techniques, transforming you from a masking novice to a masking maestro. Get ready to unlock a whole new dimension of creative freedom in your web designs.

What is CSS Masking, Anyway?

At its core, CSS masking is a technique for defining a visibility mask for an element. This mask determines which parts of the element are visible and which are clipped away. The magic lies in what you can use as a mask: PNGs with transparency, SVGs, or even CSS gradients.

The mask-image property is the star of the show. It takes an image source (just like background-image) and uses its alpha channel (transparency) or luminance (brightness) to create the mask.

  • Alpha Channel (Default): Where the mask image is transparent, the element is hidden. Where the mask image is opaque, the element is visible. Partially transparent areas of the mask result in a partially transparent element.
  • Luminance Channel: Where the mask image is black, the element is hidden. Where it's white, the element is visible. Shades of gray create varying levels of transparency.

For now, we'll focus on the default alpha mode, which is the most common and intuitive.

Getting Started: Your First Masks

Let's jump right in with some simple examples. The best way to learn is by doing.

1. Using a PNG with Transparency

A PNG with a transparent background is a perfect candidate for a mask. Imagine you have a star shape saved as star.png.

Here's our HTML element—a div with a vibrant gradient background:

<div class="masked-element star-mask"></div>

And here's the CSS to apply the mask:

.masked-element {
  width: 300px;
  height: 300px;
  background: linear-gradient(45deg, #ff8a00, #e52e71);
}

.star-mask {
  /* The magic happens here */
  mask-image: url('star.png');
  
  /* For full browser compatibility */
  -webkit-mask-image: url('star.png');
  
  /* To ensure the mask covers the whole element */
  mask-size: cover;
  -webkit-mask-size: cover;
}

Result: Instead of a square div, you'll see a beautiful gradient-filled star. The transparent areas of star.png have "cut away" the corresponding parts of our div.

Note on Prefixes: You'll notice the -webkit- prefix throughout this guide. While masking is well-supported in modern browsers, the -webkit- prefix is still necessary for full compatibility, especially with Chromium-based browsers (Chrome, Edge, Opera) and Safari. It's good practice to include both the standard and the prefixed versions.

2. Using an Inline SVG

External files are great, but what if you want to keep everything in your CSS? SVGs are your best friend. You can embed an SVG directly into your CSS using a data URI.

Let's create a chevron shape this time.

.chevron-mask {
  /* A more complex SVG mask */
  mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M0 0 L50 50 L0 100 L50 100 L100 50 L50 0 Z" fill="%23000000"/></svg>');
  -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M0 0 L50 50 L0 100 L50 100 L100 50 L50 0 Z" fill="%23000000"/></svg>');

  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
  
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}

This gives you a crisp, scalable mask without any external HTTP requests. It's perfect for UI icons and geometric shapes.

The Power of Gradients as Masks

This is where mask-image truly shines and becomes an everyday tool. You don't even need an image file! You can use CSS gradients (linear-gradient, radial-gradient, conic-gradient) as your mask source.

Remember how masking works: opaque parts of the mask show the element, and transparent parts hide it. In a CSS gradient, black or any solid color is treated as opaque, and transparent is, well, transparent.

Fading an Image

This is a classic use case. You have a tall block of text or an image, and you want it to fade out gracefully at the bottom.

<div class="fade-container">
  <img src="your-image.jpg" alt="A beautiful landscape">
  <div class="fade-overlay"></div>
</div>
.fade-container {
  position: relative;
  width: 100%;
  max-width: 600px;
}

.fade-container img {
  display: block;
  width: 100%;
}

.fade-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: white; /* Or your page background color */

  /* The fade mask */
  mask-image: linear-gradient(
    to bottom, 
    black 70%, 
    transparent 100%
  );
  -webkit-mask-image: linear-gradient(
    to bottom, 
    black 70%, 
    transparent 100%
  );
}

Wait, why did we add an overlay div? Because if you apply the mask directly to the <img> tag, the entire image element will be faded out. By applying a solid color overlay and masking that, we create the illusion that the image itself is fading into the background. This is a more robust technique.

In this gradient, the mask is solid black (fully opaque) from the top down to 70% of the height, then it transitions to fully transparent over the final 30%. This creates a smooth fade-to-background effect.

Creating a Spotlight Effect

You can use a radial-gradient to focus attention on a specific part of an element.

<div class="spotlight-effect"></div>
.spotlight-effect {
  width: 400px;
  height: 300px;
  background: url('night-sky.jpg') center/cover;

  mask-image: radial-gradient(
    circle at center, 
    black 0%, 
    black 30%, 
    transparent 60%
  );
  -webkit-mask-image: radial-gradient(
    circle at center, 
    black 0%, 
    black 30%, 
    transparent 60%
  );
}

This creates a soft-edged circular reveal in the center of the image, as if a spotlight is shining on it. You can animate the mask-position or mask-size on hover for some truly stunning interactive effects!

Fine-Tuning: The mask Property Family

Just like background, mask isn't a single property. It's a suite of properties that give you granular control over your mask's appearance and behavior. Let's break them down.

We'll use this common setup for our demos:

<div class="demo-box"></div>
.demo-box {
  width: 350px;
  height: 250px;
  background: url('your-image.jpg') center/cover;
  
  /* Our base mask for the demos */
  -webkit-mask-image: url('logo.svg');
  mask-image: url('logo.svg');
}

mask-size

Controls the size of the mask image. It behaves exactly like background-size.

  • auto (default): The image's intrinsic size.
  • cover: Scales the mask to cover the entire element, potentially cropping the mask.
  • contain: Scales the mask to fit inside the element without cropping.
  • <length> or <percentage>: 100px 50px, 50% 25%, etc.
.demo-box {
  /* ... */
  -webkit-mask-size: 50px;
  mask-size: 50px; /* The mask will be 50px wide, height adjusts proportionally */
}

mask-repeat

Determines if and how the mask image repeats if it's smaller than the element. It's identical to background-repeat.

  • repeat (default): Repeats in all directions.
  • no-repeat: Shows only once.
  • repeat-x: Repeats horizontally.
  • repeat-y: Repeats vertically.
  • space: Repeats without clipping, spacing out instances.
  • round: Repeats by stretching instances to fit perfectly.
.demo-box {
  /* ... */
  -webkit-mask-size: 80px; /* Make the mask small enough to repeat */
  mask-size: 80px;
  
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat; /* This will tile our logo across the entire element */
}

mask-position

Sets the initial position of the mask image, relative to the mask-origin. It works just like background-position.

  • Keywords: top, bottom, left, right, center.
  • <length> or <percentage>: 20px 10px, 50% 50% (which is the same as center).
.demo-box {
  /* ... */
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  
  -webkit-mask-position: top right;
  mask-position: top right; /* Places the mask in the top-right corner */
}

mask-origin and mask-clip

These are more subtle but crucial for precise control, especially on elements with padding and borders. They mirror background-origin and background-clip.

  • mask-origin: Sets the origin for mask-position. Where is 0 0? The edge of the border, padding, or content?

    • border-box (default): Origin is the top-left corner of the border.
    • padding-box: Origin is the top-left corner of the padding.
    • content-box: Origin is the top-left corner of the content area.
  • mask-clip: Determines how far the mask extends. Should it be clipped by the border, padding, or content box?

    • border-box (default): The mask extends to the outer edge of the border.
    • padding-box: The mask is clipped to the padding box.
    • content-box: The mask is clipped to the content box.

mask-mode

This property lets you choose the masking mode we mentioned earlier.

  • alpha (default): Uses the transparency channel of the mask image.
  • luminance: Uses the brightness values of the mask image. White is fully visible, black is fully invisible.
  • match-source: A special value that uses luminance if the mask-image is a <mask-source> type (like from an SVG <mask> element), otherwise it uses alpha.

Let's try a luminance mask. You can use a regular black and white JPG for this.

.luminance-mask-demo {
  /* ... */
  -webkit-mask-image: url('bw-pattern.jpg');
  mask-image: url('bw-pattern.jpg');
  
  -webkit-mask-mode: luminance;
  mask-mode: luminance;
}

mask-composite

For the truly adventurous, mask-composite defines how multiple mask layers (yes, you can have more than one!) interact with each other.

.composite-demo {
  /* ... */
  -webkit-mask-image: 
    linear-gradient(#000, #000),
    radial-gradient(circle, #000 50%, transparent 50%);
  mask-image: 
    linear-gradient(#000, #000),
    radial-gradient(circle, #000 50%, transparent 50%);

  /* Layer 1 (gradient) settings */
  -webkit-mask-size: 100% 50%;
  mask-size: 100% 50%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  
  /* Layer 2 (circle) settings */
  -webkit-mask-position: center;
  mask-position: center;

  -webkit-mask-composite: subtract;
  mask-composite: subtract;
}

Here, the subtract value effectively cuts the shape of the second mask layer (the circle) out of the first mask layer (the top-half rectangle). The possible values (add, subtract, intersect, exclude) can create incredibly complex shapes right in your CSS.

The mask Shorthand Property

Like its background cousin, mask has a shorthand property that lets you set all these values in one declaration. It's concise and efficient.

The general order is similar to background:

mask: [mask-image] [mask-position] / [mask-size] [mask-repeat] [mask-origin] [mask-clip] [mask-mode];

Let's refactor one of our earlier examples:

/* The long-hand version */
.long-hand {
  -webkit-mask-image: url('logo.svg');
  mask-image: url('logo.svg');
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 50%;
  mask-size: 50%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* The shorthand version */
.shorthand {
  -webkit-mask: url('logo.svg') center / 50% no-repeat;
  mask: url('logo.svg') center / 50% no-repeat;
}

Much cleaner! Use the shorthand for setting multiple properties at once, and stick to the long-hand properties when you only need to override a single value (e.g., changing mask-position on hover).

Practical Use Cases & Creative Inspiration

Now that you know the tools, let's put them to work.

  1. Animated Hover Reveals: Animate mask-size or mask-position on hover for a dynamic reveal effect. Imagine a product image that reveals a full-color version from the center outwards on hover.

    .card {
      /* ... */
      -webkit-mask: radial-gradient(circle, black 10%, transparent 11%);
      mask: radial-gradient(circle, black 10%, transparent 11%);
      -webkit-mask-size: 0% 0%;
      mask-size: 0% 0%;
      -webkit-mask-position: center;
      mask-position: center;
      -webkit-mask-repeat: no-repeat;
      mask-repeat: no-repeat;
      transition: -webkit-mask-size 0.5s ease;
    }
    
    .card:hover {
      -webkit-mask-size: 300% 300%;
      mask-size: 300% 300%;
    }
    
  2. Non-Rectangular Text Wraps: While CSS Shapes are the proper tool for flowing text around a shape, you can use mask-image to create visually interesting text blocks that look like they're in a unique shape.

  3. Custom UI Elements: Create buttons with unique angled edges, cards with notched corners, or profile pictures in the shape of a hexagon. The possibilities for creative UI design are immense.

  4. Image Transitions: By layering two images and animating the mask-image on the top one (e.g., with a sweeping linear-gradient), you can create slick, custom image transitions without any JavaScript.

Browser Support and Best Practices

  • Support is Good, Prefixes are Key: As of late 2023, CSS Masking is supported by all major evergreen browsers. However, as mentioned, the -webkit- prefix is still essential for full compatibility. Always include both the prefixed and standard properties.

  • Performance: CSS gradients are the most performant option for masks as they are rendered by the browser's engine. SVGs are next, offering scalability and crispness. Large raster images (PNGs, JPGs) can be less performant, especially if they are animated, so use them judiciously.

  • Fallbacks: For critical content, consider what happens in a very old browser that doesn't support masking. The element will simply appear unmasked. Ensure your design is still usable and legible in this state. You can use @supports queries to provide alternative styling if needed.

    .my-element {
      /* Unmasked styles first */
      border-radius: 8px; 
    }
    
    @supports (mask-image: none) or (-webkit-mask-image: none) {
      /* Masked styles for modern browsers */
      .my-element {
        border-radius: 0; /* Reset fallback */
        mask: url(...) ...;
        -webkit-mask: url(...) ...;
      }
    }
    

Conclusion: Unleash Your Creativity

CSS mask-image is more than just a novelty; it's a robust, well-supported feature that bridges the gap between traditional web design and the creative possibilities of graphic design software.

We've covered the fundamentals of applying masks with images and gradients, the full suite of properties for fine-tuning their behavior, and practical examples to get your creative juices flowing. By moving beyond the rectangle, you can create interfaces that are more dynamic, more engaging, and more uniquely tailored to your brand.

So go ahead, open up your code editor, and start experimenting. What amazing shapes and effects will you create? Share your discoveries in the comments below!