Gauge
Free · MITSemi-circular gauge with Core-tone bands whose needle springs to the value with a small overshoot (or an arc that fills), a rolling readout and a band label pill, exposed as a meter.
Installation
$ npx shadcn@latest add https://goodui.dev/r/gauge.jsonThe file lands in components/goodui/gauge.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/gauge works too.
Usage
import { Gauge } from "@/components/goodui/gauge"
export function NoShowGauge({ rate }: { rate: number }) {
return (
<Gauge
value={rate}
min={0}
max={20}
label="No-show rate"
bands={[
{ from: 0, to: 5, tone: "success", label: "Healthy" },
{ from: 5, to: 10, tone: "warning", label: "Watch" },
{ from: 10, to: 20, tone: "danger", label: "Critical" },
]}
formatValue={(v) => `${v}%`}
/>
)
}When to use
- One value that lives inside ranges with meaning: no-show rate (healthy / watch / critical), chair utilization, NPS.
- Dashboards where the reading should be felt as well as read: the needle swings and settles.
- A score between a min and a max that is not 0..100 (rating 1..5).
- Plain progress toward a goal: use radial-progress or meter.
- Several values side by side: a row of bars compares better than a row of dials.
- Values without ranges: a stat tile with a delta is clearer (kpi-trend).
| If you need | Use |
|---|---|
| Status ranges | bands with tone: success / warning / danger / info / neutral / primary and a label |
| Filled arc instead of a needle | variant="arc" (fills in the current band's color over a thin band ring) |
| Plain progress look | no bands, variant="arc", color |
| Percent values | formatValue={(v) => `${v}%`} |
Examples
Filled arc, utilization
<Gauge value={82} variant="arc" label="Chair utilization" bands={utilizationBands} formatValue={(v) => `${v}%`} />Rating 1..5, no bands
<Gauge value={4.6} min={1} max={5} variant="arc" color="var(--chart-2)" label="Average rating" thickness={10} format={{ minimumFractionDigits: 1, maximumFractionDigits: 1 }} />API reference
| Prop | Type | Default |
|---|---|---|
| value* | number | — |
| color Color of the needle (needle) or of the arc when no band covers the value (arc). | string | — |
| min | number | 0 |
| max | number | 100 |
| bands Colored ranges along the arc, e.g. healthy / watch / critical. Drawn with 2px gaps. | GaugeBand[] | [] |
| variant needle: a needle springs to the value over the bands. arc: the arc fills to the value in the band's color. | "needle" | "arc" | "needle" |
| size Width in px; the gauge is half as tall plus the readout. | number | 240 |
| thickness Arc thickness in px. | number | 14 |
| label Metric name under the value; also the accessible name when it is a string. | React.ReactNode | — |
| showRange Min and max printed under the arc ends. | boolean | true |
| format Intl.NumberFormat options for the value, e.g. { style: "percent" } with values 0..1. | Intl.NumberFormatOptions | — |
| locale | string | "en-US" |
| formatValue | (value: number) => string | — |
| labels Text for built-in controls and screen readers; pass translations here. | Partial<GaugeLabels> | — |
Also accepts every <div> attribute.
Accessibility
- The root is role="meter" with aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext ("6.4%, Watch"); the accessible name is the string label or labels.name.
- Value changes are announced through a polite live region with the same text.
- The current band is shown as a pill with its label and a color dot under the value, so the status is never color-only.
- Band tones use the kit's Core tone colors with dark-mode variants; pass color on a band to override.
Motion
- On first view the needle springs from the minimum to the value (stiffness 70, damping 11, mass 0.9), overshooting slightly before it settles; the arc variant fills on the same spring. The readout rolls with NumberFlow.
- New values re-spring from the current angle.
- Reduced motion: the needle and readout jump to the value.
- The spring runs as a motion value; React re-renders with the rounded angle, which is cheap for an SVG of a dozen elements.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Bands are [from, to] in value units and should tile the range from min to max without gaps; gaps between bands are drawn automatically (2px).
- variant="needle" highlights the band the value is in and dims the others; variant="arc" draws the bands as a thin outer ring and fills the inner arc.
- Give a string label so the meter has an accessible name; otherwise set labels.name.
- size is the width in px; the SVG is half as tall plus the min/max line.
FAQ
Can the needle go past max?
The angle is clamped to the dial; the readout and aria-valuenow show the real value.
Why does the needle wobble before stopping?
That is the spring (damping 11). It is intentional and off under reduced motion.
Related
Activity-style concentric goal rings that fill from 12 o'clock on view, spring to new values, dim the others when one is pointed at, and roll the center percentage.
SVG ring or 270° gauge whose arc springs to the value via stroke-dashoffset, with a rolling percentage or custom label in the center.
Labeled usage meter like "SMS credits 7,420 / 10,000" whose fill springs to the new value and shifts to amber and rose near the limit, with optional per-seat segments.
KPI tile family: the number rolls in on view and on every change (NumberFlow), the delta badge pops and pulses, the sparkline draws itself (SparklineDraw), and a group lines tiles up with hairline dividers.