✨ Features
variant="draw"+progress— draws that share of the real perimeter, corners included, so 50% is exactly half the way around whatever the aspect ratio is.withTrackandtrackColorrender the rest of the perimeter underneath, the way a progress bar shows its remaining part. Changingprogresstransitions on its own.variant="dash"— a dashed border that marches around the perimeter.dashSize,dashGapandcountare percentages of the perimeter rather than pixels, so the pattern stays even at any size;dashCap="round"with a smalldashSizegives dots.beamMode="comet"— a stroked head with a fading tail, at constant speed on any shape.wedgecannot keep a constant speed because its sweep is angular, anddothas no trail;tailsets the length as a percentage of the perimeter.trigger—hover,focus-within,inVieworneverstart the animation on interaction instead of looping forever.focus-withinfollows the focus of the control inside the wrapper, which is the answer for form fields: no state to wire up, and it keeps working with the keyboard.offset— pushes the ring away from the content and grows its radius by the same amount, so it stays concentric instead of looking pinched at the corners.
All three stroke-based additions come from one primitive: a <rect> with pathLength="100", which turns stroke-dasharray units into perimeter percentages. It was gated on Chrome 151, Firefox 148 and Safari 26.6 before the API was designed around it — 16 rendering checks, identical on all three — so there is no ResizeObserver and no JavaScript per frame.
🐛 Bug Fixes
- The glow was invisible inside a
Card, aPaper, or any container with a background. Itsz-index: -1escaped the component and painted behind the background of the first ancestor that had one. The root now creates a stacking context. borderWidthbroke the pulse border instead of thickening it. Above 1px the gradient covered only a hairline of the masked band and the corners were left empty, so a wide pulse border was four disconnected lines.- The
zIndexprop was silently ignored on the glow variant — a CSS rule beat it on specificity. - The dot beam now rides the middle of the border band, not its outer edge, so a small
sizewith a wideborderWidthis no longer half clipped away. progresswas clamped only for the draw variant, so a negative value reachedoffset-distanceand the browser dropped the declaration.trigger="never"hid the border instead of rendering it at rest, which made it a slower way to writeshow={false}.
⚡ Improvements
- The reduced-motion guard honours
theme.respectReducedMotioninstead of overriding it, and still reaches the pseudo-elements that Mantine's own rule cannot. - The decorative ring carries
aria-hidden, and the accessibility suite now runs axe over both ring implementations. - State triggers reveal in at most 0.4s regardless of
duration, so a slow loop no longer takes five seconds to appear. - Dash patterns are snapped to a period that divides the perimeter: otherwise the last dash was cut in half exactly where the perimeter closes, which read as a snag at the first corner.
variantfinally appears in the Props table —mantine-docgen-scriptexcludes factory-payload props upstream, soscripts/docgen.tsinjects it.- Every
<rect>carrieswidth/heightattributes as well as the CSS geometry properties, so an engine without SVG 2 geometry support degrades to a blunter ring instead of drawing nothing.
💥 Breaking Changes
| v2 | v3 | Why |
|---|---|---|
angle (0-360) |
progress (0-100) |
One value for "where along the border are we", shared by the static position, the draw variant and the triggers. Divide the old value by 3.6. |
delay |
phase |
The value was always emitted negated: it shifted the animation, it never delayed it. |
size (conic) |
spread |
size meant pixels in one beam mode and a share of the circle in the other. spread is the wedge width in real degrees. |
beamMode="path" |
beamMode="dot" |
Both old names described a path around the perimeter; the shapes are what differ. |
beamMode="conic" |
beamMode="wedge" |
|
| — | withMask on glow |
It used to be silently ignored there. It now works, and defaults to false on glow so the halo keeps its v2 look. |
Passing a v2 prop logs a one-time warning in development. The Upgrade guide has the full table, before/after snippets, and the rendering changes that need no code change but do change pixels.
📝 Summary
Five variants, three beam modes, and a border that can measure a value or wait for an interaction — plus the two bugs that a careful look at the published CSS turned up. Renaming four props is the price; a find-and-replace and the upgrade guide cover it.
What's Changed
- chore(lint): adopt oxc-config-mantine for the oxlint ruleset by @gfazioli in #25
- chore(security): patch vulnerable transitive dependencies by @gfazioli in #26
- chore(release): derive the repo slug from the git remote by @gfazioli in #27
- chore(lint): bump stylelint to 17 without rewriting published CSS by @gfazioli in #28
- v3: draw, dash and comet variants, triggers, offset, and two rendering fixes by @gfazioli in #29
Full Changelog: 2.0.9...3.0.0