For years CSS mask-image has been the right primitive for fading a hero photo into the page background, building spotlight reveals, or vignetting a thumbnail. For just as many years nobody used it because the syntax is a wall of vendor prefixes and gradient stops. Most projects reach for absolutely-positioned overlay divs or opacity hacks instead. Tailwind v4.1 dropped a composable mask-* utility family that finally makes the native CSS feature ergonomic.
This is the cheat sheet. The directional mask-t-from/mask-b-from family for edge fades, the radial utilities for spotlight reveals, the trick of stacking them with mask-composite: intersect, and what Tailwind actually emits when you ship to production. If you have not migrated to v4 yet, the Tailwind v3 to v4 Laravel upgrade walkthrough handles the codemod first — every example below assumes v4.1 or later.
The simplest mask: fade an image bottom to transparent#
The most common use case for mask-image is the bottom-of-hero fade. The header photo runs to the edge of the section, then dissolves into the page background without an overlay div. Two utilities do it:
<img src="/img/hero.jpg"
class="h-96 w-full object-cover
mask-b-from-60% mask-b-to-100%">
mask-b-from-60% keeps the image fully visible for the top 60% of its height. mask-b-to-100% says by the time you hit the bottom edge, the mask reaches zero — fully transparent. Tailwind composes those into a single linear-gradient(to bottom, black 60%, transparent 100%) mask under the hood.
If you only want a subtle fade, push the from value higher and shorten the gradient: mask-b-from-80% mask-b-to-95%. For a long, smoky fade, drag them apart: mask-b-from-20% mask-b-to-100%. The same directional set exists for the other three edges — mask-t-* for the top, mask-l-* for the left, mask-r-* for the right.
Reaching for a Tailwind v4 linear gradient on an overlay div used to be the standard workaround. That worked but it required a positioning wrapper and never quite matched against varying photo backgrounds. The mask version actually composites against transparency, so it works on every surface — light page, dark page, gradient page, animated page.
Stacking directions for a vignette#
The clever bit is that mask utilities are composable. Tailwind sets mask-composite: intersect by default, which means every mask class you add multiplies into the same final mask image. That makes vignettes a one-liner:
<img src="/img/portrait.jpg"
class="rounded-lg
mask-y-from-80% mask-y-to-100%
mask-x-from-80% mask-x-to-100%">
mask-y-from-X is shorthand for the matching mask-t-from-X plus mask-b-from-X pair. mask-x-from-X is the same for left and right. With both axes set to start fading at 80% and finish at the edge, the image is fully opaque in the middle and fades to transparent on every side — the soft vignette look that used to take a six-line custom CSS rule.
For an asymmetric look you can mix axes directly. mask-b-from-40% mask-t-from-90% gives you a heavy bottom fade and a thin top fade — useful for a hero image that bleeds into a dark footer but only kisses the navbar.
Radial masks for spotlight reveals#
The radial variant is where masks really start to earn their keep. Instead of stacking two divs and a clip-path, one utility builds a circular fade from any point:
<div class="h-80 w-full bg-[url('/img/keyboard.jpg')] bg-cover
mask-radial-from-50% mask-radial-to-100%
mask-radial-at-center">
</div>
That keeps the centre of the image fully visible inside a 50%-of-the-element radius, then fades smoothly to transparent by 100%. Move the at-* modifier to anchor it: mask-radial-at-top-left for an upper-left spotlight, mask-radial-at-[35%_60%] for an arbitrary coordinate. Combine with mask-circle to force a perfect circle instead of the default ellipse that fits the element's aspect ratio.
Radial masks are the move for product photography on a marketing page. The brand colour bleeds in from the corners and the eye lands on the highlight in the middle. Before v4.1 the equivalent was twenty lines of mask-image: radial-gradient(...) with vendor prefixes — and most people gave up and rasterised it into a PNG instead.
Masking non-image elements#
Nothing about these utilities is tied to <img> elements. They work on any element with a visible background or content, which unlocks two of my favourite tricks.
The first is scroll fades on horizontally scrolling lists. A row of cards that scrolls off the right edge of the viewport looks much cleaner with a soft fade than with a hard clip:
<div class="flex gap-4 overflow-x-auto
mask-r-from-85% mask-r-to-100%">
<article class="w-72 shrink-0">…</article>
<article class="w-72 shrink-0">…</article>
<article class="w-72 shrink-0">…</article>
</div>
A 15% fade on the right edge means the trailing cards look like they continue off-screen instead of getting guillotined. Works on any container — pair it with container queries so the fade only switches on when the container is narrow enough to scroll. Wider breakpoints lose the fade and the cards lay out cleanly.
The second trick is fading a gradient div over text content for animated reveals. A mask-radial-from-30% mask-radial-to-80% on a gradient div gives you a spotlight that follows your hover position with a CSS variable. The mask itself is purely declarative, so the only JavaScript is whatever sets the --x and --y for the centre coordinate.
What Tailwind actually emits#
If you peek at the compiled CSS, every mask-*-from/mask-*-to pair produces a single mask-image declaration backed by CSS custom properties. The combination machinery is mask-composite: intersect, set by the base layer. That is what lets four orthogonal mask classes still render as one mask image.
.mask-b-from-60\% {
mask-image: linear-gradient(
to bottom,
black 60%,
transparent var(--tw-mask-bottom-to)
);
}
.mask-b-to-100\% {
--tw-mask-bottom-to: 100%;
}
Two utility classes, two declarations, one final gradient. The unprefixed mask-image property covers Chrome, Firefox, Edge, and Safari 16.4 and later. Tailwind v4.1 also shipped framework-level fallbacks for older Safari, so the page still renders — just without the fade — on a stale iPhone.
Gotchas and edge cases#
A few things to watch for once you start dropping mask-* classes into a real codebase.
mask-composite is the secret sauce. Tailwind defaults to intersect, which is why stacked masks multiply cleanly. If you reach for the underlying mask-composite: add directly, your edge fades start overlapping in unintended ways. Stick with the default unless you have a specific reason.
The directional shorthands are not symmetric. Adding mask-t-from-40% after mask-b-from-40% does not produce the same fade on both sides — it adds a second gradient that intersects with the first. To get a single-direction fade, set one class. To get both sides of an axis, use the mask-y-* or mask-x-* shorthand.
Image masks need explicit mask-mode for white-on-black sources. If you point mask-[url(/img/stencil.png)] at a stencil with black shapes on a white background, the visible parts will be inverted from what you expect. Add mask-mode-luminance to use brightness instead of alpha. Transparent PNGs work with the default mask-mode-alpha.
Safari 15 falls back to no mask. Tailwind v4.1's compatibility pass means the rest of the page still renders, but the fade itself will not appear. For hero photography this is usually fine — the design still works without it. For functional masks like scroll-edge clipping or decorative reveals, test against your supported browser baseline before you ship.
Combine with dark: for theme-aware fades. The mask itself does not care about colour, but the surface it composites against does. On a CSS-first dark mode setup the fade will land on whatever your dark background is, so the same mask-b-from-60% works in both themes with no override needed.
Wrapping up#
Mask utilities are one of those v4.1 additions that quietly remove a category of one-off CSS files. Start by replacing the overlay-div hack on your hero image with mask-b-from-X mask-b-to-Y — two classes, no positioning wrapper, works against any background. Layer in radial masks for product callouts and spotlight reveals when the design calls for them.
If you are still piecing together the v4.1 surface, the Tailwind v4 text-shadow utilities article covers the other quietly significant change shipped in the same release, and the starting-style and transition-discrete cookbook handles animating display: none without JavaScript.
FAQ#
What does the Tailwind mask-image utility do?
The mask-* utility family wraps the native CSS mask-image property in composable Tailwind classes. You can build linear, radial, or conic gradient masks — or supply your own image as the mask source — without writing custom CSS or worrying about vendor prefixes. Tailwind merges multiple mask-* classes into a single mask image with mask-composite: intersect, so stacked edge fades and radial spotlights compose cleanly on the same element.
How do I fade the bottom of an image to transparent in Tailwind v4?
Use mask-b-from-X% for where the visible region ends and mask-b-to-Y% for where the mask reaches full transparency. For example, <img class="mask-b-from-60% mask-b-to-100%"> keeps the image fully opaque for the top 60% of its height then fades smoothly to transparent across the remaining 40%. The same pattern works for mask-t-*, mask-l-*, and mask-r-*, plus the mask-x-* and mask-y-* shorthands for both sides of an axis at once.
What is the difference between mask and opacity in Tailwind?
The opacity-* utilities dim the whole element to a single uniform value. A mask varies transparency across the element — the visible region stays at 100% opacity, then fades to fully transparent where the mask gradient reaches its destination. That preserves foreground brightness in the visible area while letting the background show through at the edges, which a uniform opacity cannot do. Use opacity for whole-element fades and mask utilities for partial reveals or directional fades.
Do mask utilities work on browsers without mask-image support?
Every evergreen browser supports the unprefixed mask-image property — Chrome, Edge, Firefox, and Safari 16.4 and later. Tailwind v4.1 ships fallbacks that degrade gracefully on older Safari versions, so the page still renders normally with the masked element shown unmasked. There is no JavaScript polyfill required, and you do not need to test for support — at worst the fade simply does not appear and the rest of the design remains intact.
Can I use a custom image as a mask in Tailwind v4?
Yes. Use the arbitrary value syntax: mask-[url(/img/scribble.png)] sets the named image as the mask source. Pair it with mask-mode-luminance if the file is a black-on-white stencil, or leave the default mask-mode-alpha for transparent PNGs. You can combine a custom image mask with the gradient mask-* utilities, and Tailwind will compose them with mask-composite: intersect the same way it composes multiple gradient masks.