Skip to content

Sparkline

Data Display

Dependency-free SVG line and bar summaries, including negative and constant data.

Line trendPositive barsNegative barsEmpty and single value
Open preview

Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.

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
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.

Sparkline props
PropType / DefaultDescription
datanumber[]

Default: required

Finite numeric samples in chronological left-to-right order.
labelstring

Default: required

Accessible chart title describing the metric.
variantline | bar

Default: line

Polyline or discrete bars around a zero baseline.
toneprimary | positive | negative

Default: primary

Semantic chart color.
classNamestring

Default: —

Extend the component container with theme-aware utility classes.

Build with these too