0

Meter Ring

A radial reading of a bounded value, such as a score, a utilization, or a fill level.

x
import { MeterRing } from "@/components/ui/meter-ring"

export default function MeterRingDemo() {

Installation

bunx --bun shadcn@latest add @ondo-ui/meter-ring

Usage

import { MeterRing, MeterRingLabel } from "@/components/ui/meter-ring"
<MeterRing value={72} min={0} max={100} size="sm | default | lg" variant="default">
  <MeterRingLabel>Health</MeterRingLabel>
</MeterRing>

MeterRing builds on Base UI's Meter, so it is announced as a meter with the right aria-valuenow and aria-valuetext. The dial always renders; anything you pass as children sits under it, which is where MeterRingLabel goes.

format shapes the centered number. When the center needs something a number format cannot express, formatValue replaces it outright.

The arc is a conic gradient masked into a ring — two elements, no SVG — and it takes its color from currentColor, so variant (or any text-color utility in className) recolors it.

When the reading changes the arc sweeps rather than jumping, because the component registers its fraction with @property — that is what makes a number inside a gradient interpolable. That registration ships inside the component file — React hoists it into <head> once, however many rings are on the page — so there is nothing to add to your CSS.

Examples

Sizes

x
x
x
import { MeterRing } from "@/components/ui/meter-ring"

export default function MeterRingSizes() {

Variants

variant picks the arc color from the theme's semantic set. Which band a reading falls into is yours to decide, so pick the variant from the value at the call site.

Criticalx
Nominalx
At riskx
Healthyx
import { MeterRing, MeterRingLabel } from "@/components/ui/meter-ring"

export default function MeterRingVariants() {

Formatting the value

format takes Intl.NumberFormat options and shapes both the centered text and the value announced to screen readers.

Coveragex
412 of 500 seatsx
import { MeterRing, MeterRingLabel } from "@/components/ui/meter-ring"

export default function MeterRingFormat() {

A center that isn't a number

formatValue takes over the centered text. Reach for it when there is no reading yet — a ring that shows 0.00 for "not scored" is stating something false — or when the value is better read as a grade or an icon. The announced value is unchanged, so the meter still reports the truth to assistive tech.

It is a function prop, so the component passing it has to be a Client Component.

Not scored yetx
Gradex
"use client"

import { MeterRing, MeterRingLabel } from "@/components/ui/meter-ring"

API reference

MeterRing

PropTypeDefault
valuenumber
minnumber0
maxnumber100
size"sm" | "default" | "lg""default"
variant"default" | "info" | "success" | "warning" | "destructive""default"
formatIntl.NumberFormatOptions
formatValue(formatted: string, value: number) => ReactNode
localeIntl.LocalesArgumentruntime locale
getAriaValueText(formatted: string, value: number) => string

Plus the rest of Base UI's Meter.Root props.

MeterRingDial, MeterRingTrack, MeterRingIndicator

The ring and its parts. MeterRing composes them for you; reach for them only to restyle the ring or to put something other than the value in the middle.

MeterRingValue, MeterRingLabel

Base UI's Meter.Value and Meter.Label with ondo styling. MeterRingValue takes a render function ((formatted, value) => ReactNode) when format alone is not enough.