The concentric ring progress component for Mantine gets a major upgrade: native RingProgress, glow effects, staggered animations, unified tooltips, and full accessibility — all in one release.
Introduction
Version 3.0.0 of @gfazioli/mantine-rings-progress is a ground-up rewrite. The internal fork of Mantine's RingProgress has been completely removed, replaced by the native component from @mantine/core. This eliminates ~600 lines of custom SVG and animation code while adding 10 new features that bring the component closer to the Apple Watch activity rings experience.
Whether you're building fitness dashboards, goal trackers, or compact data visualizations, this release gives you the tools to make your rings shine — literally.
✨ New Features
Per-ring Customization
Each ring can now override global props individually. Mix thick and thin rings, toggle round caps per ring, or set a custom background track color:
const rings = [
{ value: 40, color: 'cyan', thickness: 18 },
{ value: 65, color: 'red', thickness: 10, roundCaps: false },
{ value: 90, color: '#f90', thickness: 6, rootColor: 'gray' },
];
<RingsProgress size={180} rings={rings} />
Supported per-ring overrides: thickness, roundCaps, rootColor, glowIntensity, glowColor, ariaLabel, tooltip.
Staggered Entrance Animation
Instead of all rings appearing at once, use staggerDelay to animate them one after another — outer ring first, inner ring last:
<RingsProgress animate staggerDelay={300} transitionDuration={1000} rings={rings} />
Glow / Neon Effect
Add a neon-like glow that follows the ring shape using CSS drop-shadow. Set glow={true} for a default 6px blur, or pass a custom radius. Each ring can override the effect with glowIntensity and glowColor:
<RingsProgress glow={8} rings={rings} />
Pulse on Completion
When a ring reaches 100%, trigger a satisfying scale + brightness pulse animation — inspired by the Apple Watch ring closure celebration:
<RingsProgress pulseOnComplete rings={rings} />
The animation respects prefers-reduced-motion and uses a data-pulsing attribute for CSS customization.
onRingComplete Callback
Run custom logic when a ring crosses the 100% threshold:
<RingsProgress
onRingComplete={(index, ring) => {
showNotification({ message: `${ring.color} ring completed!` });
}}
rings={rings}
/>
Start Angle & Direction
Control where rings start filling and in which direction:
<RingsProgress startAngle={90} direction="counterclockwise" rings={rings} />
startAngle={0} is the 12 o'clock position (default). Direction accepts 'clockwise' or 'counterclockwise'.
Unified Tooltip
The new withTooltip prop shows a single, chart-style tooltip with color swatches for all rings — no more overlapping per-ring tooltips:
<RingsProgress
withTooltip
rings={[
{ value: 20, color: 'green', tooltip: 'Fitness – 40 Gb' },
{ value: 80, color: 'blue', tooltip: 'Running – 50 min' },
]}
/>
If tooltip is not set on a ring, the percentage value is shown automatically.
Accessibility
The component is now fully accessible out of the box:
- Root element:
role="group"witharia-label="Progress rings"(overridable) - Each ring:
role="progressbar"witharia-valuenow,aria-valuemin,aria-valuemax - Per-ring
ariaLabelfor custom screen reader text - Automatic
prefers-reduced-motionsupport viauseReducedMotion
💥 Breaking Changes
This is a major release with breaking changes. Here's what you need to update:
Animation props renamed/removed
<RingsProgress
- animationDuration={1000}
- animationSteps={60}
- animationTimingFunction="ease"
+ transitionDuration={1000}
animate
rings={rings}
/>
-
animationDuration→transitionDuration(default changed from1000to0) -
animationSteps— removed (CSS transitions replaced JSsetInterval) -
animationTimingFunction— removed (CSS useseaseby default)
Tip: If you only use
animatefor entrance animation, you can omittransitionDurationentirely — the component auto-applies 1000ms during the entrance phase.
Ring type changed
- import { RingProgressSection } from '@gfazioli/mantine-rings-progress';
- const rings: RingProgressSection[] = [...]
+ import { RingsProgressRing } from '@gfazioli/mantine-rings-progress';
+ const rings: RingsProgressRing[] = [...]
The base properties (value, color, tooltip) remain the same, so existing code that only uses those will work without changes.
Props interface
RingsProgressProps now extends BoxProps + StylesApiProps + ElementProps<'div'> (standard Mantine pattern) instead of the forked RingProgressProps. All common props (size, thickness, roundCaps, label, animate) are still available.
Styles API
A new 'label' style name was added. Style names are now 'root' | 'ring' | 'label'.
🔧 Improvements
Drastically simplified internals
The entire forked RingProgress/ directory (~600 lines) has been deleted. The component now delegates entirely to Mantine's native RingProgress, ensuring better compatibility with future Mantine releases and smaller bundle size.
Smart entrance animation
The component auto-applies 1000ms transition during the entrance phase only. After all rings mount, transitionDuration reverts to 0 — preventing geometry changes (like resizing) from triggering unwanted CSS transitions.
Interactive demos
The documentation now includes replay buttons for animation demos, interactive sliders for the pulse demo, and a configurator with all new props.
🐛 Bug Fixes
- Label centering: Fixed the label being offset from center — now rendered as a separate overlay
-
Debug styles: Removed hardcoded
color: 'red'on the label -
Theme overrides: Fixed
useProps('Rings', ...)→useProps('RingsProgress', ...) -
Countdown demo: Added
transitionDuration={0}to prevent CSS transitions from interfering with rapid updates
Getting Started
npm install @gfazioli/mantine-rings-progress@3
Import styles at the root of your application:
import '@gfazioli/mantine-rings-progress/styles.css';
Basic usage with the new features:
import { RingsProgress } from '@gfazioli/mantine-rings-progress';
function ActivityRings() {
const rings = [
{ value: 75, color: 'green', tooltip: 'Move' },
{ value: 50, color: 'blue', tooltip: 'Exercise' },
{ value: 90, color: 'orange', tooltip: 'Stand' },
];
return (
<RingsProgress
size={180}
rings={rings}
animate
staggerDelay={300}
glow={6}
withTooltip
pulseOnComplete
onRingComplete={(i) => console.log(`Ring ${i} done!`)}
/>
);
}

Top comments (0)