Gauge UI: Composable SVG Gauges for React & shadcn/ui

Build speedometers, dials, zone meters, nested gauges, and radial controls from editable React source installed through shadcn.

Gauge UI is a React gauge component library that creates SVG speedometers, dials, progress gauges, and radial controls from composable primitives.

You can use its Studio to generate code for a gauge composition, and the shadcn registry installs editable TypeScript source into components/gauge/.

The root Gauge defines the numeric domain and geometry. Tracks, arcs, zones, ticks, labels, needles, values, nested dials, and controls read that gauge state through React context.

Features

  • SVG primitives for tracks, arcs, stacks, zones, ticks, marks, labels, needles, values, and text.
  • GaugeInset for nested dials and concentric instruments.
  • GaugeControl for pointer and keyboard value input.
  • Spring and tween transitions with reduced-motion handling.
  • Gauge Studio for assembling dials and copying generated code.
  • Local TypeScript source installed through the shadcn registry.

Use Cases

  • Vehicle and telemetry dashboards with speed, RPM, fuel, temperature, or pressure dials.
  • Monitoring panels with threshold zones, tick marks, and an active-value arc.
  • Thermostats, dimmers, volume controls, and other radial inputs.
  • Instrument panels with a main dial and smaller nested gauges.

How To Use Gauge UI

Initialize shadcn if needed

Run the shadcn initializer when your project does not already have components.json:

npx shadcn@latest init

Install Gauge UI

Install the registry item directly from Gauge UI:

npx shadcn@latest add https://gauge-ui.dev/r/gauge.json

Optional Namespace Setup

Add the Gauge UI namespace to components.json when you want name-based registry commands:

{
  "registries": {
    "@gauge-ui": "https://gauge-ui.dev/r/{name}.json"
  }
}
npx shadcn@latest add @gauge-ui/gauge

Basic Usage

Set the value domain on Gauge, then compose the SVG parts needed for the dial:

import {
  Gauge,
  GaugeArc,
  GaugeHub,
  GaugeNeedle,
  GaugeTickLabels,
  GaugeTicks,
  GaugeTrack,
  GaugeValue,
  GaugeZones,
  zoneColor,
} from "@/components/gauge"
const loadZones = [
  { to: 60, color: "#22c55e" },
  { to: 80, color: "#f59e0b" },
  { to: 100, color: "#ef4444" },
]
export function ServerLoadGauge({ value }: { value: number }) {
  return (
    <div className="size-72">
      <Gauge
        value={value}
        min={0}
        max={100}
        startAngle={40}
        endAngle={320}
        transition
      >
        <GaugeTrack width={14} />
        <GaugeZones zones={loadZones} width={14} />
        <GaugeArc
          width={14}
          color={zoneColor(loadZones, value, "currentColor")}
        />
        <GaugeTicks count={10} />
        <GaugeTickLabels count={5} />
        <GaugeNeedle style="pointer" />
        <GaugeHub />
        <GaugeValue decimals={0} />
      </Gauge>
    </div>
  )
}

Gauge Primitives

PrimitivePurpose
GaugeTrackBackground arc across the gauge sweep
GaugeArcCurrent-value or fixed-range arc
GaugeStackValue arc divided by weights or fixed cutoffs
GaugeZonesColored bands based on numeric cutoffs
GaugeTicksEvenly spaced tick marks
GaugeMarksMarks at chosen values or equal domain cuts
GaugeTickLabelsNumeric or formatted labels around the arc
GaugeNeedleLine, pointer, compass, or arrow needle
GaugeHubCenter cap for a needle
GaugeDotDot that follows the active value around the arc
GaugeValueFormatted current numeric value
GaugeTextPositioned SVG text for titles, units, or labels
GaugeInsetGauge nested inside another gauge
GaugeControlRadial slider wrapper for pointer and keyboard input

Gauge Configuration

PropDefaultPurpose
valueRequiredNumeric value rendered by the gauge
min0Lower domain boundary
max100Upper domain boundary
startAngle40Start of the clockwise sweep
endAngle320End of the clockwise sweep
radius200Reference radius in SVG units
padding48Extra SVG room for strokes, ticks, and labels
fit"square"Uses the full circle box or the rendered arc bounds
transitionOffSpring or tween animation for value changes
initialValueNot setStarting value for an animated mount

Animate Gauge Values

Set transition to use the default spring, pass spring parameters, or select a timed tween:

// Default spring
<Gauge value={value} transition />
// Spring tuned by duration and bounce
<Gauge
  value={value}
  transition={{
    type: "spring",
    visualDuration: 0.4,
    bounce: 0.25,
  }}
/>
// Spring tuned with physics values
<Gauge
  value={value}
  transition={{
    type: "spring",
    stiffness: 120,
    damping: 14,
    mass: 1,
  }}
/>
// Timed tween
<Gauge
  value={value}
  transition={{
    type: "tween",
    duration: 0.6,
    ease: "easeOut",
  }}
/>
OptionApplies ToDefault
visualDurationSpring0.5 seconds
bounceSpring0.1
stiffness, damping, massSpring physics100, 10, 1 when any physics value is set
durationTween0.5 seconds
easeTween"easeInOut"

Turn the Gauge Into a Radial Input

GaugeControl wraps a square gauge with role="slider". It accepts pointer input around the dial and keyboard input through Arrow keys, Page Up, Page Down, Home, and End. Set knob for relative drag changes across the dial face. The required label prop supplies its accessible name.

"use client"
import { useState } from "react"
import {
  Gauge,
  GaugeArc,
  GaugeControl,
  GaugeHub,
  GaugeNeedle,
  GaugeTrack,
  GaugeValue,
} from "@/components/gauge"
export function ThermostatDial() {
  const [temperature, setTemperature] = useState(72)
  return (
    <GaugeControl
      className="size-72"
      value={temperature}
      onChange={setTemperature}
      min={50}
      max={90}
      step={1}
      startAngle={40}
      endAngle={320}
      label="Thermostat temperature"
      valueText={`${temperature} degrees Fahrenheit`}
      knob
    >
      <Gauge
        value={temperature}
        min={50}
        max={90}
        startAngle={40}
        endAngle={320}
        transition
      >
        <GaugeTrack width={18} />
        <GaugeArc width={18} color="#0ea5e9" />
        <GaugeNeedle style="pointer" length={0.72} />
        <GaugeHub />
        <GaugeValue
          y={72}
          format={(value) => `${Math.round(value)}°`}
        />
      </Gauge>
    </GaugeControl>
  )
}
PropPurpose
valueControlled numeric value
onChangeReceives the next value from pointer or keyboard input
labelRequired accessible name
valueTextSpoken text for the current value
min, maxInput boundaries
stepDrag and Arrow-key increment
startAngle, endAngleInput sweep matched to the rendered gauge
knobUses relative drag changes from the dial face
onPressHandles a center press, Enter, or Space
disabledDisables pointer and keyboard changes

Nest Gauges Inside a Dial

GaugeInset creates another gauge context inside the host SVG. Its x, y, and scale props position the nested dial. The inset keeps its own value, domain, angles, and transition.

<Gauge value={speed} min={0} max={240} startAngle={40} endAngle={320}>
  <GaugeTicks count={12} />
  <GaugeNeedle style="pointer" />
  <GaugeHub />
  <GaugeInset
    value={fuel}
    y={144}
    scale={0.28}
    min={0}
    max={100}
    startAngle={90}
    endAngle={270}
  >
    <GaugeTrack width={26} />
    <GaugeArc width={26} />
  </GaugeInset>
</Gauge>

Studio, Files, and Customization

  • Build a composition in the Gauge UI Studio and copy the generated React code.
  • Edit the installed TypeScript files directly in components/gauge/.
  • Use fit="content" when the SVG box should follow the rendered arc bounds. Increase padding when thick strokes or labels extend past the reference circle.
  • Use width, color, opacity, cap, and offset on arc-style primitives for stroke styling.
  • Use format on GaugeValue and GaugeTickLabels for custom text.
  • Use zoneColor, zoneCutoffs, compassLabel, clockLabel, clockTime, durationLabel, and fadeColor from the gauge module for generated or custom compositions.

Alternatives and Related Resources

Thordur Skulason

Thordur Skulason

Leave a Reply

Your email address will not be published. Required fields are marked *