DEV Community

Cover image for Mantine Rings Progress - A Complete Rewrite Inspired by Apple Watch
Giovambattista Fazioli
Giovambattista Fazioli

Posted on

Mantine Rings Progress - A Complete Rewrite Inspired by Apple Watch

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} />
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

Glow / Neon Effect

Glow

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} />
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

Start Angle & Direction

Control where rings start filling and in which direction:

<RingsProgress startAngle={90} direction="counterclockwise" rings={rings} />
Enter fullscreen mode Exit fullscreen mode

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' },
  ]}
/>
Enter fullscreen mode Exit fullscreen mode

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" with aria-label="Progress rings" (overridable)
  • Each ring: role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax
  • Per-ring ariaLabel for custom screen reader text
  • Automatic prefers-reduced-motion support via useReducedMotion

💥 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}
  />
Enter fullscreen mode Exit fullscreen mode
  • animationDurationtransitionDuration (default changed from 1000 to 0)
  • animationSteps — removed (CSS transitions replaced JS setInterval)
  • animationTimingFunction — removed (CSS uses ease by default)

Tip: If you only use animate for entrance animation, you can omit transitionDuration entirely — 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[] = [...]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Import styles at the root of your application:

import '@gfazioli/mantine-rings-progress/styles.css';
Enter fullscreen mode Exit fullscreen mode

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!`)}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Links

Top comments (0)