ApexCharts Sparkline KPI Cards — Free Live Dashboard Snippet
ApexCharts Sparkline KPI Cards · Charts · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
ApexCharts Sparkline KPI Cards — Numbers With Their Trend Attached

A KPI number on its own hides the most important thing about it: which way it has been going. A sparkline — a tiny chart with no axes — puts the trend right under the number without taking over the card. This snippet builds a row of four KPI cards, each with a different sparkline style, updating live.
sparkline.enabled does the heavy lifting
Setting chart.sparkline.enabled: true removes axes, grid lines, the legend, the toolbar and chart padding in one go. The chart becomes a trend graphic that fills its container, which is exactly what a card needs. Everything else is regular ApexCharts, so each card can use a different type: a gradient area for revenue, bars for orders, and lines for rates.
Tooltips trimmed for small charts
The default tooltip shows the category and series name, which is noise on a 64px chart. The snippet hides the x value, the marker and the series title, leaving only the formatted value.
Colour by meaning, not direction
Each KPI carries a goodUp flag. Revenue going up is green; refund rate going up is red, even though its arrow also points up. This is the detail most dashboards get wrong — direction and desirability are different things.
A rolling window
Every two seconds each KPI drops its oldest point and appends a new one, then calls updateSeries. The change badge compares the newest value to the oldest in the window, so it always describes the visible trend.
Build the cards before the charts
A chart measures its container once, when it renders. If each card were added and charted in the same loop iteration, the first sparkline would render while it was the only card in an auto-fit grid — full row width — and keep that size after the other cards arrived. The snippet inserts all four cards first and creates the charts in a second pass.
Responsive grid
Cards sit in a repeat(auto-fit, minmax(210px, 1fr)) grid, so the row becomes two columns or one on narrow screens without media queries. The live-indicator pulse respects prefers-reduced-motion.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet into an AI assistant like Claude and ask it to explain what sparkline.enabled removes and why the badge colour depends on goodUp. You can then ask it to connect the cards to a WebSocket feed, add a comparison against the same period last week, or pause updates while the tab is hidden with the Page Visibility API. Ask it to review how the change percentage should behave when the first value in the window is zero.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a row of four live KPI cards with ApexCharts sparklines (loaded from a CDN) in plain HTML, CSS and JavaScript.
Requirements:
- KPIs: revenue (gradient area sparkline), orders (bar sparkline), conversion rate and refund rate (line sparklines), each with its own colour and number formatter.
- Enable ApexCharts sparkline mode so charts have no axes, grid or padding, and trim tooltips to show only the formatted value.
- Show the latest value and a change badge comparing the newest and oldest points in the window.
- Colour the badge green or red by whether the change is good for that metric (a rising refund rate is red), while the arrow shows direction.
- Every two seconds, drop each KPI's oldest point, append a new random-walk point and update the chart and badge.
- Lay cards out in an auto-fit CSS grid and add a pulsing live indicator that respects prefers-reduced-motion.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Source Code
<div class="asp-wrap">
<div class="asp-top">
<div class="asp-title">Store overview</div>
<div class="asp-live"><span class="asp-dot"></span>Live · updates every 2s</div>
</div>
<div class="asp-grid" id="aspGrid"></div>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.asp-wrap{width:100%;max-width:980px}
.asp-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.asp-title{font-size:16px;font-weight:700;color:#0f172a}
.asp-live{font-size:12px;color:#64748b;display:flex;align-items:center;gap:6px}
.asp-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 0 0 rgba(34,197,94,.5);animation:aspPulse 2s infinite}
@keyframes aspPulse{70%{box-shadow:0 0 0 7px rgba(34,197,94,0)}100%{box-shadow:0 0 0 0 rgba(34,197,94,0)}}
.asp-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px}
.asp-card{background:#fff;border:1px solid #e2e8f0;border-radius:14px;padding:16px 16px 6px;box-shadow:0 1px 4px rgba(15,23,42,.04)}
.asp-label{font-size:12px;font-weight:600;color:#64748b}
.asp-row{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-top:4px}
.asp-value{font-size:24px;font-weight:800;color:#0f172a;font-variant-numeric:tabular-nums}
.asp-delta{font-size:12px;font-weight:700;padding:2px 7px;border-radius:999px}
.asp-delta.up{background:#dcfce7;color:#15803d}
.asp-delta.down{background:#fee2e2;color:#b91c1c}
.asp-spark{margin:0 -6px}
@media (prefers-reduced-motion:reduce){.asp-dot{animation:none}}var KPIS = [
{ key: 'rev', label: 'Revenue', color: '#6366f1', type: 'area', fmt: function (v) { return '$' + v.toLocaleString('en-US'); }, base: 4200, step: 180, goodUp: true },
{ key: 'ord', label: 'Orders', color: '#0ea5e9', type: 'bar', fmt: function (v) { return v.toLocaleString('en-US'); }, base: 62, step: 7, goodUp: true },
{ key: 'cr', label: 'Conversion rate', color: '#22c55e', type: 'line', fmt: function (v) { return v.toFixed(2) + '%'; }, base: 3.1, step: 0.18, goodUp: true },
{ key: 'refund',label: 'Refund rate', color: '#f43f5e', type: 'line', fmt: function (v) { return v.toFixed(2) + '%'; }, base: 1.4, step: 0.12, goodUp: false },
];
var POINTS = 20;
function walk(base, step) {
var arr = [base];
for (var i = 1; i < POINTS; i++) arr.push(Math.max(0, arr[i - 1] + (Math.random() - 0.45) * step));
return arr;
}
var grid = document.getElementById('aspGrid');
// Build every card BEFORE creating any chart. A chart measures its
// container once when it renders; if the grid still held one card at that
// moment, the first sparkline would size itself to the full row width.
KPIS.forEach(function (k) {
k.data = walk(k.base, k.step);
grid.insertAdjacentHTML('beforeend',
'<div class="asp-card"><div class="asp-label">' + k.label + '</div>' +
'<div class="asp-row"><div class="asp-value" id="v-' + k.key + '"></div><div class="asp-delta" id="d-' + k.key + '"></div></div>' +
'<div class="asp-spark" id="s-' + k.key + '"></div></div>');
});
KPIS.forEach(function (k) {
// sparkline.enabled strips axes, grid, legend and padding: the chart
// becomes a pure trend line sized to its card.
k.chart = new ApexCharts(document.getElementById('s-' + k.key), {
chart: { type: k.type, height: 64, sparkline: { enabled: true }, animations: { enabled: true, speed: 400, dynamicAnimation: { speed: 400 } } },
series: [{ name: k.label, data: k.data.map(round) }],
colors: [k.color],
stroke: { width: k.type === 'bar' ? 0 : 2, curve: 'smooth' },
fill: k.type === 'area' ? { type: 'gradient', gradient: { opacityFrom: 0.35, opacityTo: 0 } } : { opacity: 1 },
plotOptions: { bar: { columnWidth: '60%', borderRadius: 2 } },
tooltip: {
fixed: { enabled: false },
x: { show: false },
y: { title: { formatter: function () { return ''; } }, formatter: function (v) { return k.fmt(v); } },
marker: { show: false },
},
});
k.chart.render();
paint(k);
});
function round(v) { return Math.round(v * 100) / 100; }
function paint(k) {
var last = k.data[k.data.length - 1];
var first = k.data[0];
var change = first ? ((last - first) / first) * 100 : 0;
var good = k.goodUp ? change >= 0 : change <= 0;
document.getElementById('v-' + k.key).textContent = k.fmt(k.key === 'rev' || k.key === 'ord' ? Math.round(last) : last);
var el = document.getElementById('d-' + k.key);
el.textContent = (change >= 0 ? '▲ ' : '▼ ') + Math.abs(change).toFixed(1) + '%';
// Colour by whether the move is GOOD, not by direction: a rising
// refund rate is red even though the arrow points up.
el.className = 'asp-delta ' + (good ? 'up' : 'down');
}
// Simulated live feed: drop the oldest point, append a new one.
setInterval(function () {
KPIS.forEach(function (k) {
var next = Math.max(0, k.data[k.data.length - 1] + (Math.random() - 0.45) * k.step);
k.data = k.data.slice(1).concat(next);
k.chart.updateSeries([{ data: k.data.map(round) }]);
paint(k);
});
}, 2000);Step by step
How to Use
- 1Load ApexChartsInclude apexcharts.min.js from the CDN.
- 2Paste the snippetFour KPI cards render and start updating every two seconds.
- 3Hover a sparklineA compact tooltip shows only the value at that point.
- 4Read the badgesGreen means the move is good for that metric; red means it isn't.
- 5Define your KPIsEdit KPIS: label, colour, chart type, formatter and goodUp.
- 6Connect real dataReplace the setInterval simulation with your API or WebSocket.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Set chart.sparkline.enabled to true. It hides axes, grid, legend and toolbar and removes padding, leaving a compact trend chart that fills its container. It works with line, area and bar types.
Set tooltip.x.show to false, tooltip.marker.show to false, and return an empty string from tooltip.y.title.formatter. Keep a y.formatter so the value is formatted in the right units.
Because an increase is bad for that metric. Each KPI has a goodUp flag, and the badge colour reflects whether the change is desirable, while the arrow still shows the direction.
Keep a fixed-length array per KPI. On each new value, drop the first element, push the new one, and call chart.updateSeries with the new data. ApexCharts animates the transition.
Each sparkline is a separate chart instance with its own SVG. A handful of cards is fine; for dozens of rows, lower the update frequency, disable animations, or draw simple SVG paths yourself.




