@gfazioli/mantine-mask
A Mantine extension spotlight mask wrapper for focusing any content with cursor-follow or static radial masks.
What's new in v1.0
Breaking changes:
- Internal utility functions (
clampValue,getLinearCenterPercent,normalizeFeather,parseAngleDegrees) are no longer exported from the public API. If you were importing them, copy them into your project.
New features:
- Responsive maskRadius —
maskRadius,maskRadiusX, andmaskRadiusYnow accept Mantine breakpoint objects ({ base: 120, sm: 200, md: 320 }). The spotlight size adapts to the viewport using CSS media queries with no JavaScript re-renders.maskTransition— CSS transition for smooth fade-in/fade-out whenactivechanges (e.g.maskTransition="opacity 400ms ease").onPositionChange— callback that exposes the current spotlight position{ x, y }as it moves.Mask.Group— compound component that synchronizes the cursor position across multipleMaskchildren for coordinated spotlight effects.maskSmoothing— eased multi-stop gradients that eliminate the hard edge / bright ring artifact in both radial and linear variants.- New type exports —
MaskVariant,MaskActivation, andMaskAnimationtypes are now exported.- Bug fixes — fixed stale closures in document-level pointer tracking, fixed JSDoc default values for
radiusandclampToBounds.
Installation
After installation import package styles at the root of your application:
You can import styles within a layer @layer mantine-mask by importing @gfazioli/mantine-mask/styles.layer.css file.
Mask component
Mask wraps any content and applies a radial “spotlight” effect using CSS masking.
You can use it in two main ways:
- Cursor spotlight: the mask follows the pointer (
withCursorMask) - Static spotlight: the mask stays at a fixed position (
maskX/maskY)
If you need the cursor spotlight to keep updating even when the pointer is outside the component,
enable document-level tracking with trackPointerOnDocument.
Use maskRadius (or maskRadiusX/maskRadiusY) to control the spotlight size.
Quick mental model
- The inside of the spotlight is visible
- The outside fades to transparent (based on
maskTransparencyStart/maskTransparencyEndormaskFeather) - With
invertMask, it is the opposite: the center becomes transparent and the outside stays visible
NOTE
animationcontrols how the mask follows the cursor whenwithCursorMaskis enabled.animation="lerp"useseasinganimation="none"follows instantlyactivation="pointer"(oractivation="hover") toggles the mask on pointer enter/leave.
Key props (most common)
- Position:
withCursorMask,maskX,maskY,cursorOffsetX,cursorOffsetY - Pointer tracking:
trackPointerOnDocument - Size:
maskRadius,maskRadiusX,maskRadiusY - Feather:
maskFeather(simple), ormaskTransparencyStart+maskTransparencyEnd(advanced) - Visibility:
maskOpacity,invertMask - Interaction:
activation,active,onActiveChange - Motion:
animation,easing - Bounds:
clampToBounds,clampPadding
Basic usage
Wrap any content with Mask to apply the spotlight effect.
In this example the spotlight follows the cursor (withCursorMask) and reveals the image under the mask.
Convenience props
You can use convenience props to set common configurations quickly.
maskFeather
For example, maskFeather is a convenience prop that overrides maskTransparencyStart/maskTransparencyEnd.
maskFeather={0}: hard edge (start=end=100)maskFeather={100}: full fade (start=0, end=100)
maskRadius
Another example is maskRadius which sets both maskRadiusX and maskRadiusY to the same value.
If you need more control, you can still use the lower-level props:
maskTransparencyStartandmaskTransparencyEnddefine where the fade starts/ends (0–100)maskRadiusXandmaskRadiusYcreate an elliptical spotlight
💡 Tip: keep the spotlight inside
When using
withCursorMask, you can keep the spotlight inside the container with:
clampToBounds: prevents the center from going outsideclampPadding: adds extra padding from the edges
Document-level pointer tracking
By default, the cursor mask position is updated only while the pointer moves inside the component.
If you want the mask to follow the pointer across the entire document (the original behavior), enable:
withCursorMasktrackPointerOnDocument
NOTE
When
trackPointerOnDocumentis enabled,clampToBoundsandclampPaddingare ignored.
Static mask origin
Static mask with animation
You can animate the transition when changing maskX/maskY by setting the animation prop.
In this example, the spotlight moves smoothly to the new position. The easing can be customized with the easing prop.
maskX: 25%, maskY: 35%
Custom radius
Use maskRadius when you want a simple, circular spotlight.
It sets both X and Y radii to the same value.
Elliptical mask
If you need an oval shape, use maskRadiusX and maskRadiusY.
This is useful for wide headers, cards, or panoramic images.
Gradient smoothing
By default, CSS gradients use linear interpolation between color stops, which can produce a visible
"ring" or hard edge — especially when maskTransparencyStart is high (radial) or when the linear
variant has a narrow band. Enable maskSmoothing to replace the 2-stop gradient with an eased
multi-stop gradient that creates a much smoother fade.
Without smoothing
With smoothing
Responsive maskRadius
maskRadius, maskRadiusX, and maskRadiusY accept responsive objects with breakpoint keys.
The spotlight size adapts to the viewport using CSS media queries — no JavaScript re-renders.
Variants
Mask supports two variants:
variant="radial"(default): a classic circular/elliptical spotlightvariant="linear": a linear “band” (useful for reveal stripes and scanner effects)
When you use variant="linear":
maskRadiuscontrols the band thicknessmaskTransparencyStart/maskTransparencyEnd(ormaskFeather) control how soft the band edges aremaskAnglecontrols the band angle (in degrees)
Linear variant
Use variant="linear" to create a linear band instead of a radial spotlight.
The band follows the cursor the same way as the radial variant.
Use maskAngle to set the direction (0–360).
Inverted mask
invertMask flips what is visible: the center becomes transparent and the outside stays visible.
It works well for “hole” effects or to reveal a background layer.
Try to change the dark mode
Linear + inverted
You can combine variant="linear" with invertMask to cut a “hole band” through the content.
NOTE
invertMaskmakes the center transparent. Transparent areas show whatever is behind the component. That is why the “glow” can look light in light mode and dark in dark mode.Tip: set a background on the
Maskcontainer (for example with Mantinebg, orstyle={{ backgroundColor: ... }}) if you want a consistent look across themes.
Activation
activation controls when the cursor mask is considered “active”.
always: the mask is always enabledhover: enabled while the pointer is over the component (mouseenter/leave)pointer: same idea as hover, but based on pointer events (useful for touch/stylus)focus: enabled while the component has keyboard focus
If you do not want any automatic behavior, you can control it yourself with the active prop.
Activation can be handled automatically (with activation) or controlled manually:
- Use
activationfor common interactions (always,hover,pointer,focus) - Use the
activeprop to fully control visibility (it overridesactivation) - Use
onActiveChangeto react to internal activation eventsImplementation detail
When
activationis not set to'always', the component maintains a Box wrapper even when the mask is inactive. This wrapper is necessary to handle activation events (hover, pointer, focus). Whenactivation='always'butactive={false}is set externally, only the children are rendered without any wrapper.
Focus activation (accessibility)
If you use activation="focus", the component becomes keyboard-friendly.
In that case Mask will apply a default tabIndex={0} (unless you provide a different tabIndex).
Mask transition
Use maskTransition to animate the mask appearance when active changes.
Instead of appearing/disappearing instantly, the mask fades in and out using a CSS transition.
This works great with activation modes like hover or pointer.
Position tracking
Use onPositionChange to receive the current spotlight position as it moves.
This is useful for synchronizing other UI elements (tooltips, labels, cursors) with the spotlight.
When withCursorMask is enabled, positions are in pixels. When using static coordinates, positions are in percentages.
x: 0px, y: 0pxMask.Group
Mask.Group synchronizes the cursor position across multiple Mask components.
Each Mask inside the group keeps its own independent configuration (radius, variant, inversion, feathering...),
but they all follow the same pointer — something you cannot achieve with a single Mask wrapping everything.
Use cases:
- A grid where each card has a different mask effect but all react to the same cursor
- Dashboard layouts where multiple panels reveal content in sync
- Landing pages with coordinated spotlight animations across sections
In the example below, each card uses a different mask configuration (radial, linear, inverted, elliptical), yet all spotlights follow the same cursor position:
Inverted mask
The center is transparent, the outside is visible.
Elliptical mask
Wide horizontal spotlight with soft feathering.
Any content
Mask does not care what you render inside.
It can wrap images, cards, text blocks, or any custom React content.
Any content
Mask can wrap any React node, not just images.
Use cases
Below are a couple of common patterns you can build by combining Mask with background images and a hard edge (maskFeather={0}).
Reveal
Use a different background on the container and “reveal” it through the spotlight.
Zoom
Put a zoomed background image on the container and show it through the spotlight. This creates a simple “magnifier” effect.
Disable mask
Another example is when you want to make part of your UI inaccessible while still showing a preview of what it could be.
Create Image to Video
Unlock the power of AI-driven video creation. Transform your images into captivating videos with just a few clicks. Perfect for marketers, content creators, and social media enthusiasts looking to elevate their visual storytelling.