@gfazioli/mantine-rings-progress
A Mantine component that replicates the progress rings of Apple Watch.
Installation
After installation import package styles at the root of your application:
You can import styles within a layer @layer mantine-rings-progress by importing @gfazioli/mantine-rings-progress/styles.layer.css file.
Usage
The RingsProgress component renders multiple concentric RingProgress rings from @mantine/core. Each ring supports color, value, and tooltip props.
The label prop can be a string or a component. Try to change the label on the above configurator. You may use also an emoji 😊 or a custom component.
Per-ring Customization
Each ring can override the global thickness and roundCaps props, allowing you to create rings with different widths. You can also set a custom rootColor per ring to control the background track color independently.
Entrance Animation
Set animate to enable entrance animation — rings mount with value: 0 and smoothly transition to their target values. Control the speed with transitionDuration (in ms). When animate is enabled the component automatically uses a 1000ms transition if no explicit transitionDuration is set.
Staggered Animation
Use staggerDelay (in ms) to animate rings one after another instead of simultaneously. The outer ring animates first, followed by each inner ring after the specified delay.
Animated value changes
By default, animate only controls the entrance animation: rings mount from 0 and reach their target once. After mount, the underlying transition duration drops back to 0 and any change to a ring's value snaps to the new state.
animateValueChanges keeps the transition active after mount so every subsequent value change interpolates smoothly — useful for live dashboards or "Apply changes" flows. The duration is taken from transitionDuration, or 500 ms when transitionDuration is the default 0. prefers-reduced-motion is respected automatically.
If you already pass an explicit
transitionDurationgreater than 0, value changes will animate with that duration even withoutanimateValueChanges— Mantine's underlyingRingProgressreads the same CSS variable.animateValueChangesis most useful when you want smooth value transitions without setting a custom duration.
The demo below renders two RingsProgress side by side bound to the same state — both with animate for the entrance, but only the right one with animateValueChanges. Click "Randomize values" and watch the left snap while the right interpolates.
animate
entrance only — value changes snap
animate + animateValueChanges
entrance + every value change interpolates (500 ms default)
Gradient Rings
Each ring accepts a gradient prop ({ from, to, deg? }) that paints its stroke with a two-stop linear gradient instead of a solid colour. deg follows the CSS convention (0° = bottom→top, 90° = left→right, 180° = top→bottom, default 0°). When gradient is set, it overrides the ring's color.
Internally we inject a
<linearGradient>into Mantine'sRingProgressSVG and rewrite the foreground circle'sstroketo reference it. If you remove thegradientprop the ring falls back to the solidcolorautomatically. MultipleRingsProgresson the same page get unique gradient IDs so they never collide.
Glow Effect
Enable a neon-like glow behind rings with the glow prop. Set it to true for a default 6px glow, or pass a number for a custom blur radius. Each ring can override the glow with glowIntensity and glowColor props. Best visible on dark backgrounds.
Pulse on Completion
Set pulseOnComplete to trigger a subtle pulse animation when a ring reaches 100%. This provides visual feedback when a goal is achieved — inspired by the Apple Watch ring completion celebration. Drag the sliders to 100% or click the button to see the effect.
You can also use the onRingComplete callback to run custom logic when a ring reaches 100% (e.g., show a notification, play a sound).
Move
72%
Exercise
45%
Stand
88%
Start Angle & Direction
Use startAngle (in degrees) to change where rings start filling from — 0 is the 12 o'clock position. Set direction to "counterclockwise" to reverse the fill direction.
startAngle=90
counterclockwise
Value Labels
Set showValues to render a label at the endpoint of each ring's arc. Each ring's position is computed from its own value, thickness, startAngle, and direction. Customise the displayed string with formatValue (global) or per-ring formatValue. Per-ring showValue lets you turn the label on or off for individual rings.
Style the labels via the valueLabel Styles API selector — for example to add a background pill, or to change the font.
Tooltip
Set withTooltip to display a unified tooltip on hover showing all rings info. Each ring's tooltip prop defines the content — if omitted, the ring value percentage is shown. The tooltip displays a color swatch next to each entry. You can customize the tooltip with tooltipProps.
Interactive rings
Each ring accepts optional onClick and onHover callbacks. When onClick is provided the ring is keyboard-focusable (Enter and Space activate it), the cursor switches to pointer when the ring is under it, and the ring is exposed to assistive tech as a role="button". onHover fires on both pointer enter and leave with a third boolean argument so consumers can react to either edge.
Internally
RingsProgressdoes geometric hit-testing: it measures the cursor's radial distance from the centre and matches it against each ring's stroke band. This sidesteps the issue that each ring's underlying SVG sits inside a rectangular wrapper that overlaps the inner rings, so a click on the outer ring's curve always lands on the right callback.
Hover a ring to inspect it · click to pin (click again to unpin)
Accessibility
The component renders with role="group" and each ring has role="progressbar" with proper aria-valuenow, aria-valuemin, and aria-valuemax attributes. You can customize the accessible label with aria-label on the component or ariaLabel per-ring.
The component automatically respects prefers-reduced-motion — when enabled, all animations are disabled.
Label
The label prop can be a string or a component.
Example: Countdown
Below, another example using RingsProgress to render a countdown timer.