Sparkline
Data DisplayDependency-free SVG line and bar summaries, including negative and constant data.
Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.
"use client";
import { Sparkline } from "@/components/ui/sparkline";
export default function SparklineExample() {
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-2">
{[
{
label: "Weekly signups",
value: "2,408",
data: [14, 19, 16, 25, 22, 28, 36, 33, 42, 47, 44, 58],
variant: "line" as const,
tone: "primary" as const,
},
{
label: "Daily revenue",
value: "$12,840",
data: [18, 25, 20, 36, 32, 44, 39, 48, 42, 56, 50, 64],
variant: "bar" as const,
tone: "positive" as const,
},
{
label: "Net change",
value: "−12",
data: [12, 6, -4, 3, -8, -2, -15, -12],
variant: "bar" as const,
tone: "negative" as const,
},
].map((item) => (
<div key={item.label} className="rounded-xl border bg-card p-5">
<p className="text-xs text-muted-foreground">{item.label}</p>
<p className="mb-5 mt-2 text-2xl font-semibold tracking-tight">
{item.value}
</p>
<Sparkline {...item} />
</div>
))}
<div className="rounded-xl border bg-card p-5">
<p className="mb-6 text-xs text-muted-foreground">
Empty and single-value states
</p>
<Sparkline label="New workspace" data={[]} />
<Sparkline label="First recorded value" data={[12]} />
</div>
</div>
);
}Usage
Use compact charts next to a visible metric to show a short numeric sequence. Values stay in input order from left to right, including in RTL layouts. Non-finite values are omitted. The scale includes zero, bars support negatives, and a single line value becomes a dot. Supply a meaningful label and use a larger chart when people need axes, tooltips, or precise comparisons.
import { Sparkline } from "@/components/ui/sparkline";Shipped as editable source in src/components/ui/sparkline.tsx in both apps. Copy the implementation below when moving it to another project. Copy the complete example from Source code for all imports and state. Add "use client" when using hooks or event handlers. See installation and theming for project setup.
Component source
The complete implementation is included in both apps. It uses the React, Radix, Lucide, and shared-core dependencies already installed in Apex. Keep your project’s semantic theme tokens when reusing it.
src/components/ui/sparkline.tsx
import { useId } from "react";
import { cn } from "@dashboardpack/core/lib/utils";
export interface SparklineProps {
data: number[];
label: string;
variant?: "line" | "bar";
tone?: "primary" | "positive" | "negative";
className?: string;
}
export function Sparkline({
data,
label,
variant = "line",
tone = "primary",
className,
}: SparklineProps) {
const id = useId();
const points = data.filter(Number.isFinite);
const width = 240,
height = 64,
padding = 4;
const low = Math.min(0, ...points),
high = Math.max(0, ...points);
const range = high - low || 1;
const y = (value: number) =>
height - padding - ((value - low) / range) * (height - padding * 2);
const x = (index: number) =>
padding +
(points.length === 1
? (width - padding * 2) / 2
: (index / (points.length - 1)) * (width - padding * 2));
if (!points.length)
return (
<p className={cn("text-xs text-muted-foreground", className)}>
{label}: no data available.
</p>
);
return (
<svg
viewBox={`0 0 ${width} ${height}`}
role="img"
aria-labelledby={`${id}-title ${id}-description`}
className={cn(
"h-16 w-full",
tone === "negative"
? "text-destructive"
: tone === "positive"
? "text-success"
: "text-primary",
className,
)}
>
<title id={`${id}-title`}>{label}</title>
<desc id={`${id}-description`}>
{points.length} values: {points.join(", ")}. Ordered from left to right.
</desc>
<line
x1={padding}
x2={width - padding}
y1={y(0)}
y2={y(0)}
stroke="currentColor"
opacity="0.15"
/>
{variant === "bar" ? (
points.map((value, index) => {
const barWidth = (width - padding * 2) / points.length;
return (
<rect
key={index}
x={padding + index * barWidth + barWidth * 0.15}
y={Math.min(y(0), y(value))}
width={barWidth * 0.7}
height={Math.max(1, Math.abs(y(value) - y(0)))}
rx="1.5"
fill="currentColor"
/>
);
})
) : (
<>
<polyline
points={points
.map((value, index) => `${x(index)},${y(value)}`)
.join(" ")}
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
{points.length === 1 && (
<circle cx={x(0)} cy={y(points[0])} r="3" fill="currentColor" />
)}
</>
)}
</svg>
);
}API reference
Common props and defaults. Slot-specific props are named below; native elements also accept their standard HTML attributes. The component TypeScript definitions are the complete type reference.
| Prop | Type / Default | Description |
|---|---|---|
| data | number[]Default: required | Finite numeric samples in chronological left-to-right order. |
| label | stringDefault: required | Accessible chart title describing the metric. |
| variant | line | barDefault: line | Polyline or discrete bars around a zero baseline. |
| tone | primary | positive | negativeDefault: primary | Semantic chart color. |
| className | stringDefault: — | Extend the component container with theme-aware utility classes. |