{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gauge",
  "title": "Gauge",
  "description": "A semicircular (half-circle) gauge or dial that shows one measurement inside a known range, drawn as an SVG arc that fills from the left and animates to its new position. Reach for it when the number is a *level being read*, not a task being finished: CPU, memory, load average or server utilisation on an infrastructure dashboard; disk, storage or bandwidth used against a plan; API rate-limit, quota or credit consumption; a health, uptime, performance, SEO or Lighthouse-style score; a speedometer or throughput readout; temperature, humidity, pressure or a sensor reading on an IoT panel; battery, signal or capacity level; a credit, risk, trust or fraud score; NPS and satisfaction; and KPI, quota or target attainment on a sales or revenue dashboard. Common asks it answers: \"gauge component\", \"gauge chart react\", \"semi circle gauge\", \"half circle progress\", \"speedometer component\", \"dial component\", \"meter component react\", \"radial gauge\", \"arc progress\", \"score gauge\", \"KPI gauge\", \"utilization gauge\", \"shadcn gauge\", \"shadcn meter\", \"shadcn speedometer\", \"tailwind gauge component\", \"react-gauge-chart alternative\". Official shadcn/ui has no gauge, meter, dial or speedometer, and the two items that look adjacent are not substitutes: `progress` is a linear bar that means *how far along*, and `chart` is a Recharts wrapper — a charting library you install (recharts) and configure with data series, which can be bent into a radial bar but arrives as a chart rather than as a labelled single-value readout. The distinction is also the accessibility one, and it is the reason this is not a progress ring: a gauge exposes `role=\"meter\"` with `aria-valuemin`, `aria-valuemax` and `aria-valuenow`, which is what assistive technology reads as \"a measurement within a range\", where `role=\"progressbar\"` announces a task advancing toward completion. Getting that backwards is the single most common mistake in hand-rolled dials, and it is invisible until someone uses a screen reader. Set `segments` to change the arc colour at thresholds — green under 60, amber under 85, red to 100 — passing shadcn/Tailwind colour classes so the zones follow light and dark mode; omit it for a single primary-coloured dial. `min`/`max` set the scale, `showValue` renders the number in the centre, `formatValue` formats it (percent, bytes, ms, currency), `label` adds a caption, and `children` replaces the centre entirely when you want a sparkline, a delta or an icon in there. One file, no charting library, no icon package — nothing beyond your `cn` util.",
  "files": [
    {
      "path": "registry/ui/gauge.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface GaugeSegment {\n  /**\n   * Inclusive upper bound (in value units on the min..max scale) that this\n   * color applies up to. List segments in ascending order of upTo.\n   */\n  upTo: number\n  /**\n   * Tailwind/shadcn text color class for the arc when the value falls in this\n   * segment, e.g. \"text-primary\", \"text-amber-500\", \"text-destructive\".\n   */\n  className: string\n}\n\ninterface GaugeProps\n  extends Omit<React.ComponentPropsWithoutRef<\"div\">, \"children\"> {\n  /** Current value, clamped to min..max. */\n  value?: number\n  /** Value at the left end of the dial (default 0). */\n  min?: number\n  /** Value at the right end of the dial (default 100). */\n  max?: number\n  /** Width of the dial in pixels; height is roughly half of this (default 160). */\n  size?: number\n  /** Arc thickness in pixels (default 12). */\n  strokeWidth?: number\n  /**\n   * Threshold color zones. The arc uses the first segment whose `upTo` is >=\n   * the current value, so pass them in ascending order — e.g.\n   * `[{ upTo: 60, className: \"text-primary\" }, { upTo: 85, className: \"text-amber-500\" }, { upTo: 100, className: \"text-destructive\" }]`.\n   * Omit for a single-color dial (text-primary).\n   */\n  segments?: GaugeSegment[]\n  /** Render the value in the center of the dial (default true). */\n  showValue?: boolean\n  /** Format the displayed value (default rounds to an integer). */\n  formatValue?: (value: number) => React.ReactNode\n  /** Small caption under the number, e.g. \"CPU load\" or \"°C\". */\n  label?: React.ReactNode\n  /** Custom center content — overrides showValue and label. */\n  children?: React.ReactNode\n  /** Accessible name, e.g. \"Server load\". */\n  \"aria-label\"?: string\n}\n\nfunction clamp(n: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, n))\n}\n\nexport const Gauge = React.forwardRef<HTMLDivElement, GaugeProps>(\n  function Gauge(\n    {\n      className,\n      value = 0,\n      min = 0,\n      max = 100,\n      size = 160,\n      strokeWidth = 12,\n      segments,\n      showValue = true,\n      formatValue,\n      label,\n      children,\n      \"aria-label\": ariaLabel,\n      ...props\n    },\n    ref\n  ) {\n    const clamped = clamp(value, min, max)\n    const fraction = max <= min ? 0 : (clamped - min) / (max - min)\n\n    const radius = (size - strokeWidth) / 2\n    const cx = size / 2\n    const cy = radius + strokeWidth / 2\n    const height = cy + strokeWidth / 2\n    // A semicircle from the left end, over the top, to the right end.\n    const arc = `M ${strokeWidth / 2} ${cy} A ${radius} ${radius} 0 0 1 ${\n      size - strokeWidth / 2\n    } ${cy}`\n    const arcLength = Math.PI * radius\n    const dashOffset = arcLength * (1 - fraction)\n\n    // First segment whose ceiling the value falls under; else the last one.\n    const activeClass =\n      segments?.find((s) => clamped <= s.upTo)?.className ??\n      segments?.[segments.length - 1]?.className ??\n      \"text-primary\"\n\n    const showCenter = children != null || showValue || label != null\n\n    return (\n      <div\n        ref={ref}\n        role=\"meter\"\n        aria-valuemin={min}\n        aria-valuemax={max}\n        aria-valuenow={clamped}\n        aria-label={ariaLabel}\n        className={cn(\"relative inline-flex shrink-0\", className)}\n        style={{ width: size, height }}\n        {...props}\n      >\n        <svg\n          width={size}\n          height={height}\n          viewBox={`0 0 ${size} ${height}`}\n          aria-hidden=\"true\"\n        >\n          <path\n            className=\"text-muted-foreground/20\"\n            d={arc}\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth={strokeWidth}\n            strokeLinecap=\"round\"\n          />\n          <path\n            className={cn(\n              activeClass,\n              \"transition-[stroke-dashoffset] duration-300 ease-out\"\n            )}\n            d={arc}\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth={strokeWidth}\n            strokeLinecap=\"round\"\n            strokeDasharray={arcLength}\n            strokeDashoffset={dashOffset}\n          />\n        </svg>\n        {showCenter ? (\n          <div className=\"absolute inset-x-0 bottom-0 flex flex-col items-center gap-0.5 pb-[6%]\">\n            {children ??\n              (showValue ? (\n                <span className=\"text-xl font-semibold tabular-nums leading-none text-foreground\">\n                  {formatValue ? formatValue(clamped) : Math.round(clamped)}\n                </span>\n              ) : null)}\n            {children == null && label != null ? (\n              <span className=\"text-xs text-muted-foreground\">{label}</span>\n            ) : null}\n          </div>\n        ) : null}\n      </div>\n    )\n  }\n)\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}