# React Gauge Chart: Zones, Targets and Small Sizes

> Build a react gauge chart that shows one value against zones and a target, keeps its labels readable at 120px, and reads as a meter to screen readers.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Oct 19, 2026
- Category: [Component guides](https://wingo-ui.com/blog/category/components)
- Reading time: 9 min
- Canonical: https://wingo-ui.com/blog/react-gauge-chart

## TL;DR

A React gauge chart shows one value on a fixed scale: an arc, a needle or segments, colored by the zone the value is in, with a target tick and a label that has to stay readable when the dial shrinks to 120px. Use a gauge for one number against zones, radial bars for several goals that each fill toward their own target, and a plain progress ring when you only need a percent. The Wingo UI Gauge and Radial Bar do this from props and announce themselves as meters to screen readers.

You need a React gauge chart for a dashboard: sales against the monthly goal, an NPS score, a support SLA, disk usage. One number, a scale, and a sense of whether that number is good or bad. The first version is usually an SVG arc with a big number in the middle, and it looks fine at 280px. Then it lands in a KPI card at 120px, the label overflows the bowl, the min and max collide with the arc ends, and a screen reader announces an unlabeled image. This guide covers when a gauge is the right chart, the props that matter in the Wingo UI [Gauge](https://wingo-ui.com/components/gauge) and [Radial Bar](https://wingo-ui.com/components/radial-bar), and how to keep both readable on a phone.

## When should I use a gauge chart instead of a bar or a ring?

Use a gauge when a single value sits on a scale with a meaningful minimum and maximum and the zones matter as much as the number. A speedometer has a red zone; a monthly goal has "behind", "close" and "good". If you only need a percent of a task, a progress bar or ring says the same thing in less space. If you have several goals at once, radial bars compare them better than a row of gauges.

| You want to show | Use | Why |
|---|---|---|
| One value against zones and a target (NPS, SLA, quota) | [Gauge](https://wingo-ui.com/components/gauge) | Zone colors, a target tick and the scale ends in one dial |
| Several goals, each with its own target | [Radial Bar](https://wingo-ui.com/components/radial-bar) | Concentric rings compare progress, and a ring past 100% laps |
| One percent of a task (upload, onboarding) | [Progress](https://wingo-ui.com/components/progress) ring | Smaller, free, announced as a progressbar |
| A number and its trend | [Stat Card](https://wingo-ui.com/components/stat-card) | A gauge has no time axis |

The WAI-ARIA [meter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/) draws the same line: a meter is a value within a known range, such as a battery level, and it should not be used for progress or for values without a meaningful maximum. That is a good test for any gauge. If you cannot name the max, pick another chart.

## Which React gauge chart packages exist?

Three names come up first when you search for a react gauge chart component or a react speedometer chart, plus MUI X if you already use MUI. Facts are from each project's npm entry and docs, as of October 2026.

| Package | Latest version | License | Notes from the docs |
|---|---|---|---|
| [react-gauge-chart](https://github.com/Martin36/react-gauge-chart) | 0.5.1, published April 2024 | MIT | D3 based. `nrOfLevels` or `arcsLength` for zones, `percent` from 0 to 1 for the needle. Its peer dependencies stop at React 18. The README calls a re-render at a different size on prop updates a bug and suggests a fixed height. |
| [react-gauge-component](https://github.com/antoniolago/react-gauge-component) | 2.0.29, published April 2026 | MIT | A fork of react-gauge-chart with a preset gallery and a sandbox editor. Peer dependencies include React 19. The README shows a dynamic import with `ssr: false` for Next.js. |
| [react-d3-speedometer](https://www.npmjs.com/package/react-d3-speedometer) | 3.1.2, published January 2026 | MIT | A needle speedometer built on D3, with React 19 as its peer dependency. |
| [MUI X Charts Gauge](https://mui.com/x/react-charts/gauge/) | `@mui/x-charts` 9.15.0, published October 2026 | MIT | Arc gauge with `value`, `valueMin`, `valueMax`, `startAngle`, `endAngle`. Follows the meter ARIA pattern. There is no needle prop: the docs draw one inside `GaugeContainer` with the `useGaugeState()` hook. |

If you already ship MUI, its Gauge is the obvious choice: it is accessible and you add no new dependency. If you want a needle gauge with zones from one npm install and do not mind a client-only import, pick react-gauge-component over react-gauge-chart: it had a release in April 2026 and lists React 19 as a peer. The Wingo UI Gauge is source you copy into your project, with zones, a target and three variants built in.

## What props does a React gauge chart need?

A value, a scale and zones. `value` is required, `min` and `max` default to 0 and 100, and `thresholds` lists where each zone starts. Zones are sorted for you, and each one runs to the next threshold or to `max`.

```bash
npx wingo-ui@latest add gauge
```

```tsx
import { Gauge } from "@/components/ui/gauge";

export function SalesGoal() {
  return (
    <Gauge
      value={67.6}
      valueFormat="percent"
      target={75}
      thresholds={[
        { value: 0, tone: "danger", label: "Behind" },
        { value: 50, tone: "warning", label: "Close" },
        { value: 75, tone: "success", label: "Good" },
      ]}
      label="Monthly sales goal"
      description="$6,760 of $10,000"
      locale="en-US"
    />
  );
}
```

The arc takes the tone of the zone the value is in (`colorByZone`, on by default), a thin band outside the arc shows every zone (`showBands`), and a chip under the value names the current zone. `target` draws a tick across the track that is cut into the surface color, so it reads over any zone color.

The props you will change next:

- `variant`: `arc` fills the track up to the value, `needle` draws a thin zone-colored track and a needle (the react speedometer chart look), `segments` splits the track into zones or into `segments` equal steps.
- `sweep`: `180` for a half circle, `240` (the default) or `270` for an open ring.
- `size`: `sm`, `md` or `lg`, a nominal width of 120, 200 or 280px with the value at 18, 28 or 40px.
- `valueFormat`: `number`, `compact`, `currency` or `percent`, with `currency` and `locale`. For anything else, `formatValue(value, { kind })` gets `"value"`, `"tick"` for the min and max, or `"label"` for the spoken text.

Drag the slider below the dial. The arc changes color as the value crosses 50% and 75%, and the dollar line under the label follows it.

> Live demo (Gauge): Drag the slider: the arc turns amber at 50% and green at 75%, the zone chip changes, and the number counts to the new value. Try it at [Gauge](https://wingo-ui.com/components/gauge) and install it with `npx wingo-ui@latest add gauge`.

## How do I keep a gauge readable at small sizes?

Let the component make room for the text. Most gauge examples put the value, the label and the description in the bowl at a fixed font size. That works at 280px and breaks at 120px, where a label like "Average reply time" is wider than the hole.

The Gauge handles this in three steps, all without measuring the DOM:

1. **The value shrinks to fit.** The big number scales down when its formatted text is wider than the space inside the arc, so "$128,450" never touches the track.
2. **The label moves under the dial** when the label and description would run into the min and max row. It first tries a narrower column between the two scale ends, and only moves out when that is still too tall. The value stays in the bowl.
3. **The scale ends turn compact.** With `valueFormat="currency"` and a range of 10,000 or more, a max of 190000 prints as "$190K" under the arc end.

Because the layout comes from estimated text widths, the server HTML already has everything in place, and nothing jumps on hydration. The dial also shrinks with a narrower container, but the root carries `min-w-[120px]`, so a two-column grid of gauges fits a 360px phone and a third column does not.

For list rows, a half circle reads better than a 240-degree arc, because it is shorter. Hide the scale ends, put the label text next to the dial, and pass `min-w-0` with a width when the row needs the dial below 120px:

```tsx
<Gauge value={42} max={60} size="sm" sweep={180} showMinMax={false} tone="info" locale="en-US" className="w-[96px] min-w-0" />
```

## How do I show a lower-is-better value?

List the zones from good to bad. Response times, error rates and costs are better when they are low, so the first zone is the success tone:

```tsx
"use client";

import { Gauge } from "@/components/ui/gauge";

const hours = (v: number) => `${v.toLocaleString("en-US", { maximumFractionDigits: 1 })} h`;

export function ReplyTime({ value }: { value: number }) {
  return (
    <Gauge
      value={value}
      max={8}
      variant="segments"
      target={4}
      thresholds={[
        { value: 0, tone: "success", label: "Fast" },
        { value: 4, tone: "warning", label: "Slow" },
        { value: 6, tone: "danger", label: "Critical" },
      ]}
      label="Average reply time"
      description="Goal: under 4 h"
      formatValue={(v) => hours(v)}
    />
  );
}
```

The `"use client"` line matters here: `formatValue` is a function, and a Server Component cannot pass a function to a Client Component. The segments variant shows each zone as its own piece, the current one solid and the others at 25%, with a ring marker at the value. For a score with a negative range, such as NPS from -100 to 100, set `min={-100}` and `max={100}` and the zones work the same way. Values outside the range are clamped for drawing while the text still shows the real number, and `value={null}` draws the track with a dash for "no data yet".

> Live demo (Gauge): The needle variant on a half circle: drag the slider and the needle turns through the zones while the value counts. Try it at [Gauge](https://wingo-ui.com/components/gauge) and install it with `npx wingo-ui@latest add gauge`.

## How do I show several goals as a React radial progress chart?

Use the Radial Bar. A react radial progress chart with several rings answers a different question than a gauge: how far along is each goal, side by side. Each item in `data` is a ring with its own `max`, outermost first.

```bash
npx wingo-ui@latest add radial-bar
```

```tsx
"use client";

import { useState } from "react";
import { RadialBar, type RadialBarDatum } from "@/components/ui/radial-bar";

const goals: RadialBarDatum[] = [
  { id: "calls", label: "Calls", value: 42, max: 60 },
  { id: "meetings", label: "Meetings", value: 7, max: 10 },
  { id: "quotes", label: "Quotes sent", value: 18, max: 15 },
];

export function TeamActivity() {
  const [selected, setSelected] = useState<string | null>(null);

  return (
    <RadialBar
      data={goals}
      value={selected}
      onValueChange={setSelected}
      label="Team activity today"
      description="Quotes sent passed its target"
      center={<span className="text-xl font-semibold">3 goals</span>}
      locale="en-US"
    />
  );
}
```

A ring past its target keeps going for a second lap, so 18 of 15 quotes reads as more than done without breaking the scale. `variant="bars"` draws 270-degree bars instead of full rings, each named just left of its start at 12 o'clock. The legend sits beside the chart from 480px of container width and moves below it on narrower containers.

Small sizes are handled the same way as on the Gauge. `size` takes `sm`, `md`, `lg` or any number of pixels, so a 52px ring set fits a list row. When the hole is too small for a readout, the hovered or focused ring's value floats beside its end instead, and on touch screens it is pinned above the chart (`tooltipMode="auto"`).

> Live demo (Radial bar): Hover or tap a ring: the others dim and the hole shows its percent. Tab to the chart and use the arrow keys to walk the rings. Try it at [Radial bar](https://wingo-ui.com/components/radial-bar) and install it with `npx wingo-ui@latest add radial-bar`.

## How do I make a gauge chart accessible?

Give it the meter role, a name and a sentence that a person can understand. The Gauge renders `role="meter"` with `aria-valuemin`, `aria-valuemax` and `aria-valuenow`. A string `label` names it; other label nodes are linked through `aria-labelledby`, and `description` through `aria-describedby`. The spoken value includes the zone and the target, for example "67.6%, Close zone, target 75%". Replace that sentence with `labels.valueText(value, min, max, info)`, where `info` has the formatted text, the zone and the target. The gauge is read-only and never takes focus, because there is nothing to operate.

The Radial Bar is interactive, so it works differently. It renders a hidden meter per ring, a screen reader table with value, target and percent, and keyboard support: Tab to the chart, arrow keys walk the rings, Home and End jump to the outermost and innermost one, Enter or Space selects, Escape clears. A live region reads each ring as you move.

Color alone never carries the zone on either chart. The Gauge prints the zone name in the chip and in the spoken text; the Radial Bar prints values and percentages in the legend. Under reduced motion both skip the sweep and show the final state.

## What are the common gauge chart pitfalls?

- **A gauge for a number without a max.** Revenue this month has no natural ceiling. Use a goal as the max, or show a stat card with a trend.
- **The target notch on a different background.** The tick is cut in the surface color. If the gauge sits on a card or a custom fill, pass that color as `surface`.
- **Zone labels left out.** Without `label` on each threshold, the chip disappears and screen readers hear the number and the target but not the zone.
- **Animation in thumbnails and print.** The arc sweeps in the first time it scrolls into view. Pass `animated={false}` for static exports.
- **A row of gauges for related goals.** Five dials side by side are hard to compare. Radial bars or a bar chart put them side by side in one chart.

## How do I add it to my project?

Run `npx wingo-ui@latest add gauge` or `npx wingo-ui@latest add radial-bar`. The CLI copies the source into your project, adds the chart kit, motion and number helpers they import, and installs `motion` and `d3-shape`. Both are Pro components ([pricing](https://wingo-ui.com/pricing)); the [Progress](https://wingo-ui.com/components/progress) ring is free if one percent is all you need. For the screen around a gauge, the [Next.js admin dashboard tutorial](https://wingo-ui.com/blog/nextjs-admin-dashboard-tutorial) builds the shell, KPI row and tables, and the [dashboard component library comparison](https://wingo-ui.com/blog/best-react-component-library-for-dashboard) covers the rest of the chart set. More single-component deep dives are in the [component guides category](https://wingo-ui.com/blog/category/components), including the [React sparkline guide](https://wingo-ui.com/blog/react-sparkline) for trends inside KPI tiles.

## Components in this post

- [Gauge](https://wingo-ui.com/components/gauge) (Pro): A React gauge chart showing one value against a scale and its zones: a speedometer arc, needle or segmented dial with a target tick. Install: `npx wingo-ui@latest add gauge`
- [Radial bar](https://wingo-ui.com/components/radial-bar) (Pro): A React radial bar chart: several goals as concentric activity rings or 270° bars, each filling toward its own target and lapping past 100%. Install: `npx wingo-ui@latest add radial-bar`

## FAQ

### How do I make a gauge chart in React?

Use a gauge component that takes a value, a min and a max, then add zones and a target. With Wingo UI that is <Gauge value={67.6} valueFormat="percent" target={75} thresholds={[...]} />, installed with npx wingo-ui@latest add gauge.

### What is the difference between a gauge and a radial bar chart?

A gauge shows one value on a scale with zones, like a speedometer. A radial bar chart shows several values as concentric rings, each filling toward its own target, like fitness activity rings.

### Which ARIA role should a gauge use?

role="meter" with aria-valuenow, aria-valuemin, aria-valuemax and a label, as described in the WAI-ARIA Authoring Practices meter pattern. Use progressbar instead when the value is task progress, such as an upload.

### Can I use a React gauge chart in Next.js with server rendering?

Some packages need a dynamic import with ssr set to false; the react-gauge-component README shows that for Next.js. The Wingo UI Gauge places its text from estimated widths instead of measuring the DOM, so the server HTML already has the dial, the value and the label in place.

### Are the Wingo UI Gauge and Radial Bar free?

No. Both are Pro components. The Progress component, which has a ring variant for a single percent, is in the free tier.

---

Source: https://wingo-ui.com/blog/react-gauge-chart
