Documentation menu

spark

Server-safe inline SVG sparklines and proportion bars that sit on the text baseline.

chartsparklinesvginline

Preview

Latency trended down over the quarter, error rate spiked once , and cache hit rate sits at 82%.

Installation

Ask Claude (MCP)

With the MCP server configured, just ask:

Claude
add the moco spark component

shadcn CLI

Terminal
npx shadcn@latest add https://mocoui.site/r/spark.json

Manual

Copy each file from the Source section into its target path:

registry/spark/spark.tsx  →  components/moco/spark.tsx
registry/spark/spark.css  →  components/moco/spark.css

Needs moco items: tokens

Usage

usage.tsx
import { Spark, SparkBar } from "./spark";

export default function SparkDemo() {
  return (
    <p style={{ fontFamily: "var(--moco-serif)", color: "var(--moco-body)" }}>
      Latency trended down <Spark data={[9, 7, 8, 5, 4, 4, 2]} /> over the quarter, error rate
      spiked once <Spark data={[1, 1, 6, 2, 1]} tone="warn" dot={false} label="error rate spike" />,
      and cache hit rate sits at <SparkBar value={0.82} label="82 percent cache hit rate" /> 82%.
    </p>
  );
}

Source

components/moco/spark.tsx
import * as React from "react";
import "./spark.css";

export type SparkTone = "accent" | "warn" | "faint";

export interface SparkProps {
  data: number[];
  w?: number;
  h?: number;
  dot?: boolean;
  tone?: SparkTone;
  label?: string;
}

/**
 * Inline sparklines. ~60×16, sits on the text baseline, no axes, no labels.
 * These are punctuation, not charts.
 */
export function Spark({ data, w = 60, h = 16, dot = true, tone = "accent", label }: SparkProps) {
  if (data.length < 2) return null;
  const min = Math.min(...data);
  const max = Math.max(...data);
  const span = max - min || 1;
  const x = (i: number) => (i / (data.length - 1)) * (w - 2) + 1;
  const y = (v: number) => h - 2 - ((v - min) / span) * (h - 4);
  const d = data.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(2)},${y(v).toFixed(2)}`).join(" ");

  return (
    <svg
      className="moco-spark"
      width={w}
      height={h}
      viewBox={`0 0 ${w} ${h}`}
      role="img"
      aria-label={label ?? `sparkline, ${data.length} points, ${min.toFixed(2)} to ${max.toFixed(2)}`}
    >
      <path d={d} fill="none" stroke={`var(--moco-${tone})`} strokeWidth="1" />
      {dot ? (
        <circle
          cx={x(data.length - 1)}
          cy={y(data[data.length - 1])}
          r="1.6"
          fill={`var(--moco-${tone})`}
        />
      ) : null}
    </svg>
  );
}

export interface SparkBarProps {
  value: number;
  max?: number;
  w?: number;
  h?: number;
  tone?: SparkTone;
  label?: string;
}

/** A tiny inline proportion bar. */
export function SparkBar({ value, max = 1, w = 60, h = 8, tone = "accent", label }: SparkBarProps) {
  const k = Math.max(0, Math.min(1, value / max));
  return (
    <svg
      className="moco-spark"
      width={w}
      height={h}
      viewBox={`0 0 ${w} ${h}`}
      role="img"
      aria-label={label ?? `${Math.round(k * 100)} percent`}
    >
      <rect x="0" y={h / 2 - 2} width={w} height="4" fill="var(--moco-line)" />
      <rect x="0" y={h / 2 - 2} width={w * k} height="4" fill={`var(--moco-${tone})`} />
    </svg>
  );
}
components/moco/spark.css
.moco-spark {
  display: inline-block;
  vertical-align: baseline;
  margin: 0 0.15em;
  overflow: visible;
}