Sparkline

Component

Flat, micro-visualizations powered by Recharts, four variants for embedding in tables, cards, and dashboard grids.

Install via CLI

Run this command to automatically add the component and its dependencies to your project.

npx @abhaysinghr516/business-wish add sparkline
New to the CLI? Run npx @abhaysinghr516/business-wish init first to initialize your project.

Micro-charts that pack maximum signal into minimum space. These Sparkline components built on recharts are axis-free, grid-free, and fully embeddable — designed to live inside table cells, KPI cards, or sidebar metrics without demanding visual real estate.

Four variants: a raw line, a gradient-fill area, a 30-point activity histogram, and a complete self-contained metric widget card.

Line Sparkline

A pure, ultra-thin line trace — zero axes, zero grid, zero clutter. Embed it directly inside a table cell or any tight container to communicate 7–30 day trends at a glance. Accepts color, height, data, and dataKey props.

"use client";
import React from "react";
import { LineChart, Line, Tooltip, ResponsiveContainer, YAxis } from "recharts";

const data = [
  { value: 10 }, { value: 15 }, { value: 12 }, { value: 20 },
  { value: 18 }, { value: 25 }, { value: 22 }, { value: 30 },
  { value: 28 }, { value: 35 }, { value: 32 }, { value: 40 },
];

const SparklineTooltip = ({ active, payload }: any) => {
  if (active && payload?.length) {
    return (
      <div className="bg-neutral-900 dark:bg-white px-2.5 py-1.5 rounded-lg shadow-xl">
        <span className="text-[12px] font-bold text-white dark:text-neutral-900">
          {payload[0].value.toLocaleString()}
        </span>
      </div>
    );
  }
  return null;
};

export const LineSparkline = ({ data: d = data, height = 40, color = "#3B82F6", dataKey = "value" }) => (
  <div style={{ height, width: "100%", minWidth: 100 }}>
    <ResponsiveContainer width="100%" height="100%">
      <LineChart data={d} margin={{ top: 2, right: 2, left: 2, bottom: 2 }}>
        <YAxis domain={["dataMin - 5", "dataMax + 5"]} hide />
        <Tooltip
          content={<SparklineTooltip />}
          cursor={{ stroke: color, strokeWidth: 1, strokeOpacity: 0.25, strokeDasharray: "3 3" }}
        />
        <Line
          type="monotone" dataKey={dataKey}
          stroke={color} strokeWidth={2} dot={false}
          activeDot={{ r: 4, fill: "#fff", stroke: color, strokeWidth: 2 }}
          animationDuration={1400} animationEasing="ease-out"
        />
      </LineChart>
    </ResponsiveContainer>
  </div>
);

Area Sparkline

A line sparkline with a transparent gradient fill anchoring the curve — communicates volume and momentum together. The gradient ID is a stable string (no # chars) to avoid invalid SVG element IDs.

"use client";
import React from "react";
import { AreaChart, Area, Tooltip, ResponsiveContainer, YAxis } from "recharts";

const data = [
  { value: 10 }, { value: 15 }, { value: 12 }, { value: 20 },
  { value: 18 }, { value: 25 }, { value: 22 }, { value: 30 },
  { value: 28 }, { value: 35 }, { value: 32 }, { value: 40 },
];

export const AreaSparkline = ({ data: d = data, height = 52, color = "#10B981", dataKey = "value" }) => (
  <div style={{ height, width: "100%", minWidth: 100 }}>
    <ResponsiveContainer width="100%" height="100%">
      <AreaChart data={d} margin={{ top: 2, right: 2, left: 2, bottom: 2 }}>
        <defs>
          {/* Stable ID — never use # chars in SVG gradient IDs */}
          <linearGradient id="sp-area-fill" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%"   stopColor={color} stopOpacity={0.28} />
            <stop offset="100%" stopColor={color} stopOpacity={0} />
          </linearGradient>
        </defs>
        <YAxis domain={["dataMin - 5", "dataMax + 5"]} hide />
        <Tooltip cursor={{ stroke: color, strokeWidth: 1, strokeOpacity: 0.25 }} />
        <Area
          type="monotone" dataKey={dataKey}
          stroke={color} strokeWidth={2}
          fillOpacity={1} fill="url(#sp-area-fill)" dot={false}
          activeDot={{ r: 4, fill: "#fff", stroke: color, strokeWidth: 2 }}
          animationDuration={1400} animationEasing="ease-out"
        />
      </AreaChart>
    </ResponsiveContainer>
  </div>
);

Bar Sparkline

A 30-point activity histogram — each bar's opacity is mapped to three tiers (high / mid / low) relative to the dataset maximum. Data is deterministic (no Math.random()) to prevent SSR hydration mismatches.

"use client";
import React from "react";
import { BarChart, Bar, Cell, Tooltip, ResponsiveContainer, YAxis } from "recharts";

// Fixed data — avoids SSR hydration mismatches from Math.random()
const activityData = [
  3, 12, 7, 18, 5, 14, 9, 20, 2, 16, 11, 8, 19, 4, 13,
  6, 15, 10, 17, 1, 12, 8, 20, 5, 14, 9, 17, 3, 11, 18,
].map((count, i) => ({ day: `Day ${i + 1}`, count }));

export const BarSparkline = ({ data = activityData, height = 40, color = "#8B5CF6", dataKey = "count" }) => {
  const max = Math.max(...data.map((d: any) => d[dataKey]));
  return (
    <div style={{ height, width: "100%", minWidth: 100 }}>
      <ResponsiveContainer width="100%" height="100%">
        <BarChart data={data} margin={{ top: 2, right: 2, left: 2, bottom: 2 }}>
          <YAxis hide />
          <Tooltip cursor={{ fill: color, opacity: 0.08 }} />
          <Bar dataKey={dataKey} radius={[2, 2, 0, 0]} animationDuration={1000} animationEasing="ease-out">
            {data.map((entry: any, i: number) => (
              <Cell
                key={i} fill={color}
                fillOpacity={
                  entry[dataKey] >= max * 0.7 ? 1
                  : entry[dataKey] >= max * 0.4 ? 0.55
                  : 0.25
                }
              />
            ))}
          </Bar>
        </BarChart>
      </ResponsiveContainer>
    </div>
  );
};

Widget Sparkline

A self-contained metric card. The delta badge and sparkline color are computed dynamically from the last two data points — no hardcoded values. The sparkline bleeds edge-to-edge using negative margin.

"use client";
import React from "react";
import { Activity, ArrowUpRight } from "lucide-react";
import { LineSparkline } from "@/components/Sparkline";

const data = [
  { value: 10 }, { value: 15 }, { value: 12 }, { value: 20 },
  { value: 18 }, { value: 25 }, { value: 22 }, { value: 30 },
  { value: 28 }, { value: 35 }, { value: 32 }, { value: 40 },
];

export const WidgetSparkline = () => {
  const latest = data[data.length - 1].value;
  const prev   = data[data.length - 2].value;
  const isUp   = latest >= prev;
  const delta  = (((latest - prev) / prev) * 100).toFixed(1);
  const color  = isUp ? "#10B981" : "#EF4444";

  return (
    <div className="w-full max-w-[300px] bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-3xl pt-5 pb-0 px-5 shadow-sm hover:shadow-md transition-shadow duration-300 overflow-hidden">
      <div className="flex items-start justify-between mb-4">
        <div>
          <div className="flex items-center gap-1.5 mb-1.5">
            <Activity className="w-3.5 h-3.5 text-neutral-400" strokeWidth={2.5} />
            <p className="text-[11px] font-semibold text-neutral-500 uppercase tracking-widest">Conversion</p>
          </div>
          <div className="flex items-baseline gap-1">
            <span className="text-[28px] font-bold text-neutral-900 dark:text-white leading-none tracking-tighter">{latest}</span>
            <span className="text-[15px] font-bold text-neutral-400">%</span>
          </div>
        </div>
        <div className={`flex items-center gap-1 text-[11px] font-semibold mt-0.5 px-2 py-1 rounded-full ${
          isUp ? "bg-green-50 dark:bg-green-500/10 text-green-600 dark:text-green-400"
               : "bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-400"
        }`}>
          <ArrowUpRight className="w-3 h-3" style={{ transform: isUp ? "none" : "rotate(90deg)" }} strokeWidth={2.5} />
          <span>{Math.abs(Number(delta))}%</span>
        </div>
      </div>

      {/* Full-bleed sparkline */}
      <div className="h-[56px] w-[calc(100%+40px)] -mx-5">
        <LineSparkline height={56} color={color} />
      </div>
    </div>
  );
};