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.
Installation
{
"registries": {
"@designrevision": "https://registry.designrevision.com/r/{name}.json"
}
}
Add to your existing components.json. Requires shadcn/ui v2.3+.
Packages
Props
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.
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.
"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.
Packages
Props
No props documented yet.
"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.
Packages
Props
No props documented yet.
"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.
Packages
Props
No props documented yet.
"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.
Packages
Props
No props documented yet.
"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.
Packages
Props
No props documented yet.
"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.
Packages
Props
No props documented yet.
"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.
Packages
Props
No props documented yet.
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.
Packages
Props
No props documented yet.
"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>
)
}
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 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:
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:
<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 for the full charting system and the standalone chart types.