Sparkline

Component

Free, copy-pasteable Tailwind CSS Sparkline component. Accessible, fully responsive, dark-mode ready, and customizable.

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>
  );
};