awesome-speedometer-gauge
v0.1.3
Published
A fully-featured React KPI speedometer gauge with 5 visual modes (Standard, SemiCircle, FullCircle, Arc, MultiBand), animated needle, threshold colour bands, dark/light/purple themes, and live data fetching.
Maintainers
Readme
awesome-speedometer-gauge
A fully-featured React KPI speedometer gauge component. Five visual modes, animated needle/arc, threshold colour bands, dark/light/purple themes, optional live data fetching, tooltip, target marker, and a real-time flash effect — all in a single self-contained card widget.
Features
- 5 gauge modes —
standard(half-dial with needle),semicircle(wide arc),full(360° dial),arc(270° gradient arc),multiband(coloured bands with dot marker) - Animated transitions — smooth eased needle/arc animation on value changes via
requestAnimationFrame - Real-time mode — fast 220ms transitions + a flash effect when the value crosses a threshold band
- Threshold colour bands — supply any number of
{ value, color }breakpoints; defaults to poor/warning/good traffic-light colours - Target marker — optional secondary marker at a target value with its own colour
- Three built-in themes —
light,dark,purple; all colours driven by CSS custom properties so you can override anything - Tooltip — hover to reveal value, target, min, and max in a clean floating chip
- Live data fetching — pass an
apiEndpointURL and the component fetches, normalises, and merges JSON automatically dataprop shortcut — pass a plain object or array directly to skip the fetch- CSS Module styles — scoped styles with no global pollution;
style.cssis the only thing you need to import - ESM + CJS dual build — works in Vite, Next.js, CRA, and any modern bundler
- Zero runtime dependencies beyond React
Installation
npm install awesome-speedometer-gauge
# or
yarn add awesome-speedometer-gauge
# or
pnpm add awesome-speedometer-gaugePeer dependencies — React ≥ 18 and ReactDOM ≥ 18 must already be installed.
Importing Styles
Import the stylesheet once at your app root:
import 'awesome-speedometer-gauge/style.css';For the full typographic effect, load this Google Font in your index.html <head>:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>Falls back to
"Segoe UI"/system-uiwithout the font.
Quick Start
import { AwesomeSpeedometerGauge } from 'awesome-speedometer-gauge';
import 'awesome-speedometer-gauge/style.css';
export default function App() {
return (
<AwesomeSpeedometerGauge
title="Server CPU"
value={72}
min={0}
max={100}
units="%"
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| type | 'standard' \| 'semicircle' \| 'full' \| 'arc' \| 'multiband' | 'standard' | Visual mode of the gauge. |
| value | number | — | Current value. Clamped to [min, max]. Can also come from data or apiEndpoint. |
| min | number | 0 | Minimum scale value. |
| max | number | 100 | Maximum scale value. |
| target | number | — | Optional target/goal value. Shown as a separate marker when showTarget is true. |
| thresholds | Threshold[] | auto | Array of { value, color, label? } breakpoints sorted ascending. Defaults to 60/80/100 traffic-light bands. |
| showTicks | boolean | true | Show tick marks on the gauge arc. |
| showValue | boolean | true | Show the current value label inside the gauge SVG. |
| showTarget | boolean | false | Show the target marker and include target in the value label. |
| animated | boolean | true | Animate value changes with a 500ms eased transition. |
| realtime | boolean | false | Use a faster 220ms transition and flash the card when the threshold band changes. |
| size | number | 220 | SVG canvas size in px (width and height). |
| thickness | number | 18 | Arc stroke thickness in px. |
| startAngle | number | mode default | Arc start angle in degrees. Each mode has a sensible default. |
| endAngle | number | mode default | Arc end angle in degrees. |
| units | string | '' | Unit label appended to the value (e.g. '%', 'rpm', '°C'). |
| title | string | 'KPI Gauge' | Card heading. |
| subtitle | string | '' | Optional secondary line below the title. |
| theme | 'light' \| 'dark' \| 'purple' | 'light' | Built-in colour theme. |
| data | object \| array | — | Inline data object (or array whose first element is used). Merged with and overridden by direct props. |
| apiEndpoint | string | '/gauge-data.json' | URL to fetch gauge data from. Only used when data is not provided. |
| className | string | — | Additional CSS class on the root card element. |
| style | object | — | Inline styles on the root card element. |
| onClick | (value: number) => void | — | Called with the current value when the card is clicked. |
Threshold object
{ value: 60, color: '#ef4444' }, // 0–60 → red
{ value: 80, color: '#f59e0b' }, // 60–80 → amber
{ value: 100, color: '#22c55e' }, // 80–100 → greenExamples
Example 1 — All five modes side by side
import { AwesomeSpeedometerGauge } from 'awesome-speedometer-gauge';
import 'awesome-speedometer-gauge/style.css';
var thresholds = [
{ value: 40, color: '#ef4444' },
{ value: 70, color: '#f59e0b' },
{ value: 100, color: '#22c55e' },
];
var modes = ['standard', 'semicircle', 'full', 'arc', 'multiband'];
export default function AllModesDemo() {
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24, padding: 32, background: '#f1f5f9' }}>
{modes.map(function(mode) {
return (
<AwesomeSpeedometerGauge
key={mode}
type={mode}
title={mode.charAt(0).toUpperCase() + mode.slice(1)}
subtitle="CPU Usage"
value={63}
min={0}
max={100}
units="%"
thresholds={thresholds}
size={200}
/>
);
})}
</div>
);
}Example 2 — Dark theme, real-time updates, target marker
Simulates a live-updating gauge that flashes when the value crosses a threshold band.
import { useState, useEffect } from 'react';
import { AwesomeSpeedometerGauge } from 'awesome-speedometer-gauge';
import 'awesome-speedometer-gauge/style.css';
var thresholds = [
{ value: 50, color: '#22c55e' },
{ value: 80, color: '#f59e0b' },
{ value: 100, color: '#ef4444' },
];
export default function RealtimeDemo() {
var [rpm, setRpm] = useState(3200);
useEffect(function() {
var id = setInterval(function() {
setRpm(function(prev) {
var delta = (Math.random() - 0.48) * 400;
return Math.round(Math.max(0, Math.min(8000, prev + delta)));
});
}, 1200);
return function() { clearInterval(id); };
}, []);
return (
<div style={{ padding: 40, background: '#0b1220', minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<AwesomeSpeedometerGauge
type="standard"
theme="dark"
title="Engine RPM"
subtitle="Live telemetry"
value={rpm}
min={0}
max={8000}
units="rpm"
target={4500}
showTarget={true}
thresholds={thresholds}
realtime={true}
size={240}
thickness={20}
/>
</div>
);
}Example 3 — Dashboard grid with onClick, custom thresholds, and mixed themes
import { useState } from 'react';
import { AwesomeSpeedometerGauge } from 'awesome-speedometer-gauge';
import 'awesome-speedometer-gauge/style.css';
var kpis = [
{
type: 'arc', theme: 'light', title: 'Conversion Rate', subtitle: 'Last 30 days',
value: 4.7, min: 0, max: 10, units: '%', target: 5, showTarget: true,
thresholds: [{ value: 3, color: '#ef4444' }, { value: 6, color: '#f59e0b' }, { value: 10, color: '#22c55e' }],
},
{
type: 'multiband', theme: 'dark', title: 'Memory Usage', subtitle: 'App server',
value: 71, min: 0, max: 100, units: '%',
thresholds: [{ value: 50, color: '#22c55e' }, { value: 80, color: '#f59e0b' }, { value: 100, color: '#ef4444' }],
},
{
type: 'semicircle', theme: 'purple', title: 'NPS Score', subtitle: 'Q2 2025',
value: 58, min: -100, max: 100, target: 50, showTarget: true,
thresholds: [{ value: 0, color: '#ef4444' }, { value: 50, color: '#f59e0b' }, { value: 100, color: '#22c55e' }],
},
{
type: 'full', theme: 'dark', title: 'Disk I/O', subtitle: 'Primary volume',
value: 340, min: 0, max: 500, units: 'MB/s',
thresholds: [{ value: 200, color: '#22c55e' }, { value: 400, color: '#f59e0b' }, { value: 500, color: '#ef4444' }],
},
];
var overlayStyle = {
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)',
display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 100,
};
var popupStyle = {
background: '#fff', borderRadius: 16, padding: '32px 40px',
fontFamily: 'sans-serif', minWidth: 260, textAlign: 'center',
boxShadow: '0 20px 60px rgba(0,0,0,0.25)',
};
export default function DashboardDemo() {
var [clicked, setClicked] = useState(null);
return (
<div style={{ padding: 32, background: '#f1f5f9', minHeight: '100vh' }}>
<h2 style={{ fontFamily: 'sans-serif', marginBottom: 24, color: '#0f172a' }}>KPI Dashboard</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 20 }}>
{kpis.map(function(kpi, i) {
return (
<AwesomeSpeedometerGauge
key={i}
type={kpi.type}
theme={kpi.theme}
title={kpi.title}
subtitle={kpi.subtitle}
value={kpi.value}
min={kpi.min}
max={kpi.max}
units={kpi.units}
target={kpi.target}
showTarget={kpi.showTarget}
thresholds={kpi.thresholds}
animated={true}
size={210}
onClick={function(val) { setClicked({ title: kpi.title, value: val, units: kpi.units || '' }); }}
/>
);
})}
</div>
{clicked && (
<div style={overlayStyle} onClick={function() { setClicked(null); }}>
<div style={popupStyle} onClick={function(e) { e.stopPropagation(); }}>
<div style={{ fontSize: 13, color: '#64748b', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 8 }}>
{clicked.title}
</div>
<div style={{ fontSize: 52, fontWeight: 700, color: '#0f172a' }}>
{clicked.value}{clicked.units}
</div>
<button
onClick={function() { setClicked(null); }}
style={{ marginTop: 20, padding: '8px 24px', borderRadius: 8, border: 'none', background: '#0f172a', color: '#fff', cursor: 'pointer', fontSize: 13 }}
>
Close
</button>
</div>
</div>
)}
</div>
);
}Note:
AwesomeSpeedometerGauge.module.cssis bundled directly intodist/esm/index.jsanddist/cjs/index.jsby Vite's CSS Modules pipeline. You do not need to import it separately — onlystyle.cssneeds an explicit import in your app.
CSS Custom Properties
All colours in the gauge are driven by CSS variables set on the .gaugeCard root element. Override them per-instance with the style prop or globally in your own CSS:
.my-gauge {
--gauge-progress: #6366f1; /* filled arc colour */
--gauge-track: #e2e8f0; /* background arc */
--gauge-needle: #1e293b; /* needle / dot marker */
--gauge-target: #f59e0b; /* target marker */
--gauge-range-poor: #ef4444;
--gauge-range-warning: #f59e0b;
--gauge-range-good: #22c55e;
--gauge-text: #0f172a;
--gauge-text-muted: #64748b;
--gauge-tick: #cbd5e1;
--gauge-surface: #ffffff;
--gauge-bg: #f6f7f9;
}Pass the class via the className prop:
<AwesomeSpeedometerGauge className="my-gauge" value={55} />Live Data Fetching
When data is not provided, the component fetches from apiEndpoint. The endpoint should return JSON matching the prop shape:
{
"value": 73,
"min": 0,
"max": 100,
"target": 80,
"units": "%",
"type": "standard",
"thresholds": [
{ "value": 50, "color": "#ef4444" },
{ "value": 75, "color": "#f59e0b" },
{ "value": 100, "color": "#22c55e" }
]
}An array response is also accepted — the first element is used. Direct props always override fetched values.
📄 License
MIT
