# Shadcn Sparkline

> Free shadcn/ui sparkline for React — tiny inline charts for KPI cards and tables, built on Recharts and themed with CSS variables. Line, area, and bar sparklines, a stat-card grid, table-embedded trends, an interactive tooltip, plus loading and accessible variants.

Source: https://designrevision.com/components/sparkline

A **Shadcn Sparkline** is a tiny, axis-free chart shown **inline** — inside a KPI card, beside a metric, or in a table cell — to show a trend at a glance. It's built on the same [chart](/components/chart) primitive (Recharts + the `--chart` CSS variables), just stripped down: no grid, no axes, no legend. Sparklines are everywhere in dashboards yet under-served by component libraries — these are native-shadcn and dark-mode-ready.

## A sparkline is a minimal chart

Take any chart, drop the grid and axes, hide the dots, and shrink it:

```tsx
const chartConfig = {
  value: { label: "Revenue", color: "var(--chart-1)" },
} satisfies ChartConfig

<ChartContainer config={chartConfig} className="h-[60px] w-full">
  <LineChart data={chartData} margin={{ top: 4, right: 0, left: 0, bottom: 0 }}>
    <Line dataKey="value" type="monotone" stroke="var(--color-value)" strokeWidth={2} dot={false} />
  </LineChart>
</ChartContainer>
```

Swap `LineChart`/`Line` for `AreaChart`/`Area` (add a gradient) or `BarChart`/`Bar` for the area and bar variants.

## In a KPI card

The most common use — a label, a big number, a trend, and the sparkline:

```tsx
<Card>
  <CardHeader>
    <CardDescription>Revenue</CardDescription>
    <CardTitle className="text-3xl tabular-nums">$45,231</CardTitle>
    <div className="text-emerald-600 text-xs">+20.1% this month</div>
  </CardHeader>
  <CardContent>{/* sparkline */}</CardContent>
</Card>
```

The **Stat-card grid** example tiles four of these for a dashboard overview.

## In a table

Drop a small `ChartContainer` (`h-[32px] w-[100px]`) into a cell for a **trend column** — one dot-less line per row. The **In a table** example pairs it with revenue and change columns.

## Interactive

Sparklines are usually static, but you can add a hidden `XAxis` and a `ChartTooltip` so hovering reveals per-point values without growing the chart (the **Interactive** example).

## Accessibility

A sparkline is **decorative** — the number and trend text carry the meaning. Mark the chart `aria-hidden="true"` and make sure the value and a short trend sentence are real, readable text (the **Accessible** example).

See **[Shadcn Charts](/components/chart)** for the full charting system and the standalone chart types.

## Installation

### chart

**Install:**

```bash
npx shadcn@latest add @designrevision/chart
```

**Dependencies:** recharts, utils, card

**Props**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| config * | ChartConfig | — | Maps each data key to { label, icon?, color }. Colors become --color-<key> CSS vars. |
| children * | React.ReactElement (a Recharts chart) | — | A single Recharts chart (BarChart, LineChart, …) — ChartContainer wraps it in a ResponsiveContainer. |

`*` required.

**Usage & accessibility:** A Recharts wrapper themed with CSS variables. Pass a `config` (ChartConfig: each data key → { label, icon?, color }) to <ChartContainer>; it injects --color-<key> vars (scoped per light/dark theme) that your Recharts series reference as fill="var(--color-desktop)". Use <ChartTooltip content={<ChartTooltipContent />}> and <ChartLegend content={<ChartLegendContent />}> for themed tooltips/legends. Colors default to the --chart-1..5 tokens, which re-theme automatically in dark mode. The container needs a height — keep aspect-video / min-h-* or ResponsiveContainer measures 0. This is the base primitive; the Bar/Line/Area/Pie/Radar/Radial chart pages compose it.

```tsx
// components/ui/chart.tsx
"use client"

import * as React from "react"
import * as RechartsPrimitive from "recharts"

import { cn } from "@/lib/utils"

// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const

export type ChartConfig = {
  [k in string]: {
    label?: React.ReactNode
    icon?: React.ComponentType
  } & (
    | { color?: string; theme?: never }
    | { color?: never; theme: Record<keyof typeof THEMES, string> }
  )
}

type ChartContextProps = {
  config: ChartConfig
}

const ChartContext = React.createContext<ChartContextProps | null>(null)

function useChart() {
  const context = React.useContext(ChartContext)

  if (!context) {
    throw new Error("useChart must be used within a <ChartContainer />")
  }

  return context
}

function ChartContainer({
  id,
  className,
  children,
  config,
  ...props
}: React.ComponentProps<"div"> & {
  config: ChartConfig
  children: React.ComponentProps<
    typeof RechartsPrimitive.ResponsiveContainer
  >["children"]
}) {
  const uniqueId = React.useId()
  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`

  return (
    <ChartContext.Provider value={{ config }}>
      <div
        data-slot="chart"
        data-chart={chartId}
        className={cn(
          "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
          className
        )}
        {...props}
      >
        <ChartStyle id={chartId} config={config} />
        <RechartsPrimitive.ResponsiveContainer>
          {children}
        </RechartsPrimitive.ResponsiveContainer>
      </div>
    </ChartContext.Provider>
  )
}

const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
  const colorConfig = Object.entries(config).filter(
    ([, config]) => config.theme || config.color
  )

  if (!colorConfig.length) {
    return null
  }

  return (
    <style
      dangerouslySetInnerHTML={{
        __html: Object.entries(THEMES)
          .map(
            ([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
  .map(([key, itemConfig]) => {
    const color =
      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
      itemConfig.color
    return color ? `  --color-${key}: ${color};` : null
  })
  .join("\n")}
}
`
          )
          .join("\n"),
      }}
    />
  )
}

const ChartTooltip = RechartsPrimitive.Tooltip

function ChartTooltipContent({
  active,
  payload,
  className,
  indicator = "dot",
  hideLabel = false,
  hideIndicator = false,
  label,
  labelFormatter,
  labelClassName,
  formatter,
  color,
  nameKey,
  labelKey,
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
  React.ComponentProps<"div"> & {
    hideLabel?: boolean
    hideIndicator?: boolean
    indicator?: "line" | "dot" | "dashed"
    nameKey?: string
    labelKey?: string
  }) {
  const { config } = useChart()

  const tooltipLabel = React.useMemo(() => {
    if (hideLabel || !payload?.length) {
      return null
    }

    const [item] = payload
    const key = `${labelKey || item?.dataKey || item?.name || "value"}`
    const itemConfig = getPayloadConfigFromPayload(config, item, key)
    const value =
      !labelKey && typeof label === "string"
        ? config[label as keyof typeof config]?.label || label
        : itemConfig?.label

    if (labelFormatter) {
      return (
        <div className={cn("font-medium", labelClassName)}>
          {labelFormatter(value, payload)}
        </div>
      )
    }

    if (!value) {
      return null
    }

    return <div className={cn("font-medium", labelClassName)}>{value}</div>
  }, [
    label,
    labelFormatter,
    payload,
    hideLabel,
    labelClassName,
    config,
    labelKey,
  ])

  if (!active || !payload?.length) {
    return null
  }

  const nestLabel = payload.length === 1 && indicator !== "dot"

  return (
    <div
      className={cn(
        "border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
        className
      )}
    >
      {!nestLabel ? tooltipLabel : null}
      <div className="grid gap-1.5">
        {payload
          .filter((item) => item.type !== "none")
          .map((item, index) => {
            const key = `${nameKey || item.name || item.dataKey || "value"}`
            const itemConfig = getPayloadConfigFromPayload(config, item, key)
            const indicatorColor = color || item.payload.fill || item.color

            return (
              <div
                key={item.dataKey}
                className={cn(
                  "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
                  indicator === "dot" && "items-center"
                )}
              >
                {formatter && item?.value !== undefined && item.name ? (
                  formatter(item.value, item.name, item, index, item.payload)
                ) : (
                  <>
                    {itemConfig?.icon ? (
                      <itemConfig.icon />
                    ) : (
                      !hideIndicator && (
                        <div
                          className={cn(
                            "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
                            {
                              "h-2.5 w-2.5": indicator === "dot",
                              "w-1": indicator === "line",
                              "w-0 border-[1.5px] border-dashed bg-transparent":
                                indicator === "dashed",
                              "my-0.5": nestLabel && indicator === "dashed",
                            }
                          )}
                          style={
                            {
                              "--color-bg": indicatorColor,
                              "--color-border": indicatorColor,
                            } as React.CSSProperties
                          }
                        />
                      )
                    )}
                    <div
                      className={cn(
                        "flex flex-1 justify-between leading-none",
                        nestLabel ? "items-end" : "items-center"
                      )}
                    >
                      <div className="grid gap-1.5">
                        {nestLabel ? tooltipLabel : null}
                        <span className="text-muted-foreground">
                          {itemConfig?.label || item.name}
                        </span>
                      </div>
                      {item.value && (
                        <span className="text-foreground font-mono font-medium tabular-nums">
                          {item.value.toLocaleString()}
                        </span>
                      )}
                    </div>
                  </>
                )}
              </div>
            )
          })}
      </div>
    </div>
  )
}

const ChartLegend = RechartsPrimitive.Legend

function ChartLegendContent({
  className,
  hideIcon = false,
  payload,
  verticalAlign = "bottom",
  nameKey,
}: React.ComponentProps<"div"> &
  Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
    hideIcon?: boolean
    nameKey?: string
  }) {
  const { config } = useChart()

  if (!payload?.length) {
    return null
  }

  return (
    <div
      className={cn(
        "flex items-center justify-center gap-4",
        verticalAlign === "top" ? "pb-3" : "pt-3",
        className
      )}
    >
      {payload
        .filter((item) => item.type !== "none")
        .map((item) => {
          const key = `${nameKey || item.dataKey || "value"}`
          const itemConfig = getPayloadConfigFromPayload(config, item, key)

          return (
            <div
              key={item.value}
              className={cn(
                "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
              )}
            >
              {itemConfig?.icon && !hideIcon ? (
                <itemConfig.icon />
              ) : (
                <div
                  className="h-2 w-2 shrink-0 rounded-[2px]"
                  style={{
                    backgroundColor: item.color,
                  }}
                />
              )}
              {itemConfig?.label}
            </div>
          )
        })}
    </div>
  )
}

// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
  config: ChartConfig,
  payload: unknown,
  key: string
) {
  if (typeof payload !== "object" || payload === null) {
    return undefined
  }

  const payloadPayload =
    "payload" in payload &&
    typeof payload.payload === "object" &&
    payload.payload !== null
      ? payload.payload
      : undefined

  let configLabelKey: string = key

  if (
    key in payload &&
    typeof payload[key as keyof typeof payload] === "string"
  ) {
    configLabelKey = payload[key as keyof typeof payload] as string
  } else if (
    payloadPayload &&
    key in payloadPayload &&
    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
  ) {
    configLabelKey = payloadPayload[
      key as keyof typeof payloadPayload
    ] as string
  }

  return configLabelKey in config
    ? config[configLabelKey]
    : config[key as keyof typeof config]
}

export {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  ChartLegend,
  ChartLegendContent,
  ChartStyle,
}
```

## Examples

### Line sparkline

A KPI card with an inline line sparkline — a metric, trend, and axis-free micro-chart.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-demo-01
```

**Dependencies:** recharts, lucide-react, chart, card

```tsx
// components/ui/sparkline-demo-01.tsx
"use client"

import { TrendingUp } from "lucide-react"
import { Line, LineChart } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { type ChartConfig, ChartContainer } from "@/components/ui/chart"

const chartData = [
  { i: 1, value: 220 },
  { i: 2, value: 260 },
  { i: 3, value: 240 },
  { i: 4, value: 310 },
  { i: 5, value: 280 },
  { i: 6, value: 360 },
  { i: 7, value: 330 },
  { i: 8, value: 400 },
  { i: 9, value: 380 },
  { i: 10, value: 460 },
  { i: 11, value: 440 },
  { i: 12, value: 520 },
]

const chartConfig = {
  value: { label: "Revenue", color: "var(--chart-1)" },
} satisfies ChartConfig

export default function SparklineDemo01() {
  return (
    <Card className="w-full max-w-xs">
      <CardHeader>
        <CardDescription>Revenue</CardDescription>
        <CardTitle className="text-3xl tabular-nums">$45,231</CardTitle>
        <div className="flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400">
          <TrendingUp className="size-3.5" />
          +20.1% this month
        </div>
      </CardHeader>
      <CardContent>
        <ChartContainer config={chartConfig} className="h-[60px] w-full">
          <LineChart
            data={chartData}
            margin={{ top: 4, right: 0, left: 0, bottom: 0 }}
          >
            <Line
              dataKey="value"
              type="monotone"
              stroke="var(--color-value)"
              strokeWidth={2}
              dot={false}
            />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

---

### Area sparkline

A KPI card with a gradient-filled area sparkline.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-area-01
```

**Dependencies:** recharts, lucide-react, chart, card

```tsx
// components/ui/sparkline-area-01.tsx
"use client"

import { TrendingDown } from "lucide-react"
import { Area, AreaChart } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { type ChartConfig, ChartContainer } from "@/components/ui/chart"

const chartData = [
  { i: 1, value: 520 },
  { i: 2, value: 480 },
  { i: 3, value: 500 },
  { i: 4, value: 430 },
  { i: 5, value: 450 },
  { i: 6, value: 390 },
  { i: 7, value: 410 },
  { i: 8, value: 350 },
  { i: 9, value: 360 },
  { i: 10, value: 300 },
  { i: 11, value: 320 },
  { i: 12, value: 280 },
]

const chartConfig = {
  value: { label: "Bounce rate", color: "var(--chart-2)" },
} satisfies ChartConfig

export default function SparklineArea01() {
  return (
    <Card className="w-full max-w-xs">
      <CardHeader>
        <CardDescription>Bounce rate</CardDescription>
        <CardTitle className="text-3xl tabular-nums">38.2%</CardTitle>
        <div className="flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400">
          <TrendingDown className="size-3.5" />
          -4.6% this month
        </div>
      </CardHeader>
      <CardContent>
        <ChartContainer config={chartConfig} className="h-[60px] w-full">
          <AreaChart
            data={chartData}
            margin={{ top: 4, right: 0, left: 0, bottom: 0 }}
          >
            <defs>
              <linearGradient id="fillSparkArea" x1="0" y1="0" x2="0" y2="1">
                <stop
                  offset="5%"
                  stopColor="var(--color-value)"
                  stopOpacity={0.6}
                />
                <stop
                  offset="95%"
                  stopColor="var(--color-value)"
                  stopOpacity={0.05}
                />
              </linearGradient>
            </defs>
            <Area
              dataKey="value"
              type="monotone"
              stroke="var(--color-value)"
              strokeWidth={2}
              fill="url(#fillSparkArea)"
            />
          </AreaChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

---

### Bar sparkline

A KPI card with an inline bar sparkline.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-bar-01
```

**Dependencies:** recharts, lucide-react, chart, card

```tsx
// components/ui/sparkline-bar-01.tsx
"use client"

import { TrendingUp } from "lucide-react"
import { Bar, BarChart } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { type ChartConfig, ChartContainer } from "@/components/ui/chart"

const chartData = [
  { i: 1, value: 12 },
  { i: 2, value: 18 },
  { i: 3, value: 14 },
  { i: 4, value: 22 },
  { i: 5, value: 17 },
  { i: 6, value: 25 },
  { i: 7, value: 20 },
  { i: 8, value: 28 },
  { i: 9, value: 24 },
  { i: 10, value: 32 },
  { i: 11, value: 29 },
  { i: 12, value: 36 },
]

const chartConfig = {
  value: { label: "Sales", color: "var(--chart-1)" },
} satisfies ChartConfig

export default function SparklineBar01() {
  return (
    <Card className="w-full max-w-xs">
      <CardHeader>
        <CardDescription>Sales</CardDescription>
        <CardTitle className="text-3xl tabular-nums">1,284</CardTitle>
        <div className="flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400">
          <TrendingUp className="size-3.5" />
          +12.4% this month
        </div>
      </CardHeader>
      <CardContent>
        <ChartContainer config={chartConfig} className="h-[60px] w-full">
          <BarChart
            data={chartData}
            margin={{ top: 4, right: 0, left: 0, bottom: 0 }}
          >
            <Bar dataKey="value" fill="var(--color-value)" radius={2} />
          </BarChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

---

### Stat-card grid

A dashboard grid of metric cards, each with its own sparkline.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-group-01
```

**Dependencies:** recharts, chart, card

```tsx
// components/ui/sparkline-group-01.tsx
"use client"

import { Area, AreaChart } from "recharts"

import { cn } from "@/lib/utils"
import { Card, CardContent } from "@/components/ui/card"
import { ChartContainer } from "@/components/ui/chart"

const metrics = [
  {
    key: "revenue",
    label: "Revenue",
    value: "$45.2k",
    trend: "+20.1%",
    up: true,
    color: "var(--chart-1)",
    data: [320, 300, 360, 340, 410, 390, 460, 520],
  },
  {
    key: "users",
    label: "Active users",
    value: "2,340",
    trend: "+8.2%",
    up: true,
    color: "var(--chart-2)",
    data: [180, 210, 200, 240, 230, 270, 290, 320],
  },
  {
    key: "orders",
    label: "Orders",
    value: "1,284",
    trend: "+12.4%",
    up: true,
    color: "var(--chart-3)",
    data: [60, 72, 68, 80, 76, 88, 92, 104],
  },
  {
    key: "churn",
    label: "Churn",
    value: "1.8%",
    trend: "-0.4%",
    up: true,
    color: "var(--chart-4)",
    data: [42, 40, 38, 36, 34, 33, 31, 28],
  },
]

export default function SparklineGroup01() {
  return (
    <div className="grid w-full max-w-2xl grid-cols-1 gap-4 sm:grid-cols-2">
      {metrics.map((metric) => (
        <Card key={metric.key}>
          <CardContent className="space-y-1">
            <div className="flex items-center justify-between">
              <span className="text-muted-foreground text-sm">
                {metric.label}
              </span>
              <span
                className={cn(
                  "text-xs font-medium",
                  metric.up
                    ? "text-emerald-600 dark:text-emerald-400"
                    : "text-rose-600 dark:text-rose-400"
                )}
              >
                {metric.trend}
              </span>
            </div>
            <div className="text-2xl font-bold tabular-nums">
              {metric.value}
            </div>
            <ChartContainer
              config={{ value: { label: metric.label, color: metric.color } }}
              className="h-[40px] w-full"
            >
              <AreaChart
                data={metric.data.map((value, i) => ({ i, value }))}
                margin={{ top: 2, right: 0, left: 0, bottom: 0 }}
              >
                <defs>
                  <linearGradient
                    id={`fill-${metric.key}`}
                    x1="0"
                    y1="0"
                    x2="0"
                    y2="1"
                  >
                    <stop
                      offset="5%"
                      stopColor="var(--color-value)"
                      stopOpacity={0.5}
                    />
                    <stop
                      offset="95%"
                      stopColor="var(--color-value)"
                      stopOpacity={0.05}
                    />
                  </linearGradient>
                </defs>
                <Area
                  dataKey="value"
                  type="monotone"
                  stroke="var(--color-value)"
                  strokeWidth={1.5}
                  fill={`url(#fill-${metric.key})`}
                />
              </AreaChart>
            </ChartContainer>
          </CardContent>
        </Card>
      ))}
    </div>
  )
}
```

---

### In a table

Sparklines embedded as a trend column inside table rows.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-table-01
```

**Dependencies:** recharts, chart, card, table

```tsx
// components/ui/sparkline-table-01.tsx
"use client"

import { Line, LineChart } from "recharts"

import { cn } from "@/lib/utils"
import {
  Card,
  CardContent,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { ChartContainer } from "@/components/ui/chart"
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"

const rows = [
  {
    name: "Acme Inc",
    revenue: "$12,400",
    change: "+8.1%",
    up: true,
    color: "var(--chart-1)",
    data: [20, 24, 22, 28, 26, 32, 30, 36],
  },
  {
    name: "Globex",
    revenue: "$9,150",
    change: "+3.4%",
    up: true,
    color: "var(--chart-2)",
    data: [30, 28, 31, 29, 33, 30, 34, 35],
  },
  {
    name: "Initech",
    revenue: "$7,820",
    change: "-2.7%",
    up: false,
    color: "var(--chart-4)",
    data: [38, 36, 37, 33, 34, 30, 31, 28],
  },
  {
    name: "Umbrella",
    revenue: "$6,540",
    change: "+11.9%",
    up: true,
    color: "var(--chart-3)",
    data: [12, 16, 14, 20, 18, 26, 24, 32],
  },
]

export default function SparklineTable01() {
  return (
    <Card className="w-full max-w-2xl">
      <CardHeader>
        <CardTitle>Top accounts</CardTitle>
      </CardHeader>
      <CardContent>
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Account</TableHead>
              <TableHead>Revenue</TableHead>
              <TableHead className="w-[120px]">Trend</TableHead>
              <TableHead className="text-right">Change</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {rows.map((row) => (
              <TableRow key={row.name}>
                <TableCell className="font-medium">{row.name}</TableCell>
                <TableCell className="tabular-nums">{row.revenue}</TableCell>
                <TableCell>
                  <ChartContainer
                    config={{ value: { color: row.color } }}
                    className="h-[32px] w-[100px]"
                  >
                    <LineChart
                      data={row.data.map((value, i) => ({ i, value }))}
                      margin={{ top: 2, right: 0, left: 0, bottom: 2 }}
                    >
                      <Line
                        dataKey="value"
                        type="monotone"
                        stroke="var(--color-value)"
                        strokeWidth={1.5}
                        dot={false}
                      />
                    </LineChart>
                  </ChartContainer>
                </TableCell>
                <TableCell
                  className={cn(
                    "text-right text-sm font-medium tabular-nums",
                    row.up
                      ? "text-emerald-600 dark:text-emerald-400"
                      : "text-rose-600 dark:text-rose-400"
                  )}
                >
                  {row.change}
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </CardContent>
    </Card>
  )
}
```

---

### Interactive (tooltip)

A sparkline with a hover tooltip that reveals per-point values.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-tooltip-01
```

**Dependencies:** recharts, chart, card

```tsx
// components/ui/sparkline-tooltip-01.tsx
"use client"

import { Area, AreaChart, XAxis } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  type ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart"

const chartData = [
  { day: "Mon", value: 220 },
  { day: "Tue", value: 280 },
  { day: "Wed", value: 250 },
  { day: "Thu", value: 340 },
  { day: "Fri", value: 410 },
  { day: "Sat", value: 380 },
  { day: "Sun", value: 460 },
]

const chartConfig = {
  value: { label: "Visitors", color: "var(--chart-1)" },
} satisfies ChartConfig

export default function SparklineTooltip01() {
  return (
    <Card className="w-full max-w-xs">
      <CardHeader>
        <CardDescription>Visitors this week</CardDescription>
        <CardTitle className="text-3xl tabular-nums">2,340</CardTitle>
        <div className="text-muted-foreground text-xs">Hover the chart for daily values</div>
      </CardHeader>
      <CardContent>
        <ChartContainer config={chartConfig} className="h-[80px] w-full">
          <AreaChart
            data={chartData}
            margin={{ top: 4, right: 0, left: 0, bottom: 0 }}
          >
            <XAxis dataKey="day" hide />
            <ChartTooltip
              cursor={false}
              content={<ChartTooltipContent className="w-[140px]" />}
            />
            <defs>
              <linearGradient id="fillSparkTooltip" x1="0" y1="0" x2="0" y2="1">
                <stop
                  offset="5%"
                  stopColor="var(--color-value)"
                  stopOpacity={0.6}
                />
                <stop
                  offset="95%"
                  stopColor="var(--color-value)"
                  stopOpacity={0.05}
                />
              </linearGradient>
            </defs>
            <Area
              dataKey="value"
              type="monotone"
              stroke="var(--color-value)"
              strokeWidth={2}
              fill="url(#fillSparkTooltip)"
            />
          </AreaChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

---

### Loading state

A skeleton for a KPI sparkline card while data loads.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-loading-01
```

**Dependencies:** card, skeleton

```tsx
// components/ui/sparkline-loading-01.tsx
import {
  Card,
  CardContent,
  CardHeader,
} from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"

// A loading state for a KPI sparkline card.
export default function SparklineLoading01() {
  return (
    <Card className="w-full max-w-xs">
      <CardHeader className="gap-2">
        <Skeleton className="h-4 w-20" />
        <Skeleton className="h-8 w-28" />
        <Skeleton className="h-3 w-24" />
      </CardHeader>
      <CardContent>
        <Skeleton className="h-[60px] w-full rounded-md" />
      </CardContent>
    </Card>
  )
}
```

---

### Accessible

The metric and trend live in text; the sparkline is marked aria-hidden as decorative.

**Install:**

```bash
npx shadcn@latest add @designrevision/sparkline-a11y-01
```

**Dependencies:** recharts, lucide-react, chart, card

```tsx
// components/ui/sparkline-a11y-01.tsx
"use client"

import { TrendingUp } from "lucide-react"
import { Line, LineChart } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { type ChartConfig, ChartContainer } from "@/components/ui/chart"

const chartData = [
  { i: 1, value: 220 },
  { i: 2, value: 260 },
  { i: 3, value: 240 },
  { i: 4, value: 310 },
  { i: 5, value: 280 },
  { i: 6, value: 360 },
  { i: 7, value: 330 },
  { i: 8, value: 400 },
  { i: 9, value: 380 },
  { i: 10, value: 460 },
  { i: 11, value: 440 },
  { i: 12, value: 520 },
]

const chartConfig = {
  value: { label: "Revenue", color: "var(--chart-1)" },
} satisfies ChartConfig

export default function SparklineA11y01() {
  return (
    <Card className="w-full max-w-xs">
      <CardHeader>
        <CardDescription>Revenue</CardDescription>
        <CardTitle className="text-3xl tabular-nums">$45,231</CardTitle>
        {/* The accessible value + trend live in text; the chart is decorative. */}
        <p className="flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400">
          <TrendingUp className="size-3.5" />
          <span>Up 20.1% over the last 12 months</span>
        </p>
      </CardHeader>
      <CardContent>
        {/* aria-hidden: the sparkline is illustrative only; the number and trend
            above carry the meaning, so screen readers skip the decorative chart. */}
        <ChartContainer
          config={chartConfig}
          className="h-[60px] w-full"
          aria-hidden="true"
        >
          <LineChart
            data={chartData}
            margin={{ top: 4, right: 0, left: 0, bottom: 0 }}
          >
            <Line
              dataKey="value"
              type="monotone"
              stroke="var(--color-value)"
              strokeWidth={2}
              dot={false}
            />
          </LineChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```
