Recharts builds charts from composable React components — LineChart wraps Line, XAxis, YAxis, CartesianGrid, and Tooltip as children. ResponsiveContainer makes charts fluid within their parent. Custom tooltips receive the payload prop with all data for the hovered point. AreaChart with LinearGradient fills areas. Stacked BarChart renders series with stackId="stack". Reference lines mark thresholds; reference areas highlight date ranges. Recharts is SVG-based and renders server-side for static screenshots. The syncId prop synchronizes hover state across multiple charts. Real-time charts add new data to state arrays and trim to a fixed window. Claude Code generates Recharts chart configurations, custom tooltip components, multi-series visualizations, and the real-time data update patterns for analytics dashboards.
CLAUDE.md for Recharts
## Recharts Stack
- Version: recharts >= 2.12
- Container: <ResponsiveContainer width="100%" height={300}> — fluid sizing
- Line: <LineChart data={data}><Line dataKey="value" stroke="#3b82f6" /></LineChart>
- Bar: <BarChart data={data}><Bar dataKey="revenue" stackId="stack" /></BarChart>
- Area: <AreaChart> with <defs><LinearGradient> for fill
- Tooltip: <Tooltip content={<CustomTooltip />}> — payload has { name, value, color }
- Axis: <XAxis dataKey="date" tickFormatter={formatDate} /> <YAxis tickFormatter={formatCurrency} />
- Responsive: wrap in <ResponsiveContainer> for fluid width
Revenue Line Chart
// components/charts/RevenueChart.tsx — line chart with custom tooltip
"use client"
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
ReferenceLine,
} from "recharts"
interface RevenueData {
date: string
revenue: number
orders: number
target?: number
}
interface CustomTooltipProps {
active?: boolean
payload?: Array<{ name: string; value: number; color: string }>
label?: string
}
function CustomTooltip({ active, payload, label }: CustomTooltipProps) {
if (!active || !payload?.length) return null
return (
<div className="rounded-lg border bg-background p-3 shadow-lg text-sm">
<p className="font-semibold text-foreground mb-2">{label}</p>
{payload.map(entry => (
<div key={entry.name} className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: entry.color }} />
<span className="text-muted-foreground">{entry.name}:</span>
<span className="font-medium">
{entry.name === "revenue"
? `$${(entry.value / 100).toLocaleString()}`
: entry.value.toLocaleString()}
</span>
</div>
))}
</div>
)
}
export function RevenueChart({ data }: { data: RevenueData[] }) {
const avgRevenue = data.reduce((sum, d) => sum + d.revenue, 0) / data.length
return (
<ResponsiveContainer width="100%" height={320}>
<LineChart data={data} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
<defs>
<filter id="glow">
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tick={{ fill: "hsl(var(--muted-foreground))", fontSize: 12 }}
tickFormatter={date => new Date(date).toLocaleDateString("en", { month: "short", day: "numeric" })}
/>
<YAxis
tickLine={false}
axisLine={false}
tick={{ fill: "hsl(var(--muted-foreground))", fontSize: 12 }}
tickFormatter={v => `$${(v / 100 / 1000).toFixed(0)}k`}
width={48}
/>
<Tooltip content={<CustomTooltip />} />
<Legend wrapperStyle={{ paddingTop: "16px" }} />
<ReferenceLine
y={avgRevenue}
stroke="hsl(var(--muted-foreground))"
strokeDasharray="4 4"
label={{ value: "Avg", fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
/>
<Line
type="monotone"
dataKey="revenue"
stroke="#3b82f6"
strokeWidth={2}
dot={false}
activeDot={{ r: 4, strokeWidth: 0 }}
filter="url(#glow)"
/>
<Line
type="monotone"
dataKey="target"
stroke="#10b981"
strokeWidth={1.5}
strokeDasharray="4 4"
dot={false}
/>
</LineChart>
</ResponsiveContainer>
)
}
Stacked Bar Chart
// components/charts/OrdersBarChart.tsx — stacked bar by status
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
} from "recharts"
interface OrdersData {
week: string
pending: number
processing: number
shipped: number
delivered: number
}
const STATUS_COLORS = {
pending: "#f59e0b",
processing: "#3b82f6",
shipped: "#8b5cf6",
delivered: "#10b981",
}
export function OrdersBarChart({ data }: { data: OrdersData[] }) {
return (
<ResponsiveContainer width="100%" height={280}>
<BarChart data={data} barSize={28} maxBarSize={40}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" vertical={false} />
<XAxis
dataKey="week"
tickLine={false}
axisLine={false}
tick={{ fontSize: 12, fill: "hsl(var(--muted-foreground))" }}
/>
<YAxis hide />
<Tooltip
cursor={{ fill: "hsl(var(--muted) / 0.5)" }}
content={({ active, payload, label }) => {
if (!active || !payload?.length) return null
const total = payload.reduce((sum, p) => sum + (p.value as number), 0)
return (
<div className="rounded-lg border bg-background p-3 shadow-lg text-sm space-y-1">
<p className="font-semibold">{label} — {total} orders</p>
{payload.map(entry => (
<div key={entry.name} className="flex justify-between gap-4">
<span className="text-muted-foreground">{entry.name}</span>
<span>{entry.value}</span>
</div>
))}
</div>
)
}}
/>
<Legend />
{(["pending", "processing", "shipped", "delivered"] as const).map(status => (
<Bar
key={status}
dataKey={status}
stackId="orders"
fill={STATUS_COLORS[status]}
radius={status === "delivered" ? [4, 4, 0, 0] : [0, 0, 0, 0]}
/>
))}
</BarChart>
</ResponsiveContainer>
)
}
Area Chart with Gradient
// components/charts/ConversionAreaChart.tsx
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
} from "recharts"
export function ConversionAreaChart({ data }: { data: { date: string; rate: number }[] }) {
return (
<ResponsiveContainer width="100%" height={200}>
<AreaChart data={data}>
<defs>
<linearGradient id="conversionGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis dataKey="date" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
<YAxis tickFormatter={v => `${v}%`} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} width={36} />
<Tooltip
formatter={(value: number) => [`${value.toFixed(1)}%`, "Conversion Rate"]}
labelFormatter={label => new Date(label).toLocaleDateString()}
/>
<Area
type="monotone"
dataKey="rate"
stroke="#3b82f6"
strokeWidth={2}
fill="url(#conversionGradient)"
dot={false}
/>
</AreaChart>
</ResponsiveContainer>
)
}
Real-Time Rolling Chart
// components/charts/RealtimeMetricsChart.tsx
"use client"
import { useEffect, useRef, useState } from "react"
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from "recharts"
const WINDOW_SIZE = 60 // Keep last 60 data points
interface MetricPoint {
time: string
latency: number
errorRate: number
}
export function RealtimeMetricsChart({ metricSource }: { metricSource: () => Promise<{ latency: number; errorRate: number }> }) {
const [data, setData] = useState<MetricPoint[]>([])
const intervalRef = useRef<ReturnType<typeof setInterval>>()
useEffect(() => {
intervalRef.current = setInterval(async () => {
const metrics = await metricSource()
const point: MetricPoint = {
time: new Date().toLocaleTimeString(),
latency: metrics.latency,
errorRate: metrics.errorRate,
}
setData(prev => [...prev.slice(-(WINDOW_SIZE - 1)), point])
}, 1000)
return () => clearInterval(intervalRef.current)
}, [metricSource])
return (
<ResponsiveContainer width="100%" height={200}>
<LineChart data={data} isAnimationActive={false}>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis dataKey="time" tick={false} />
<YAxis yAxisId="latency" orientation="left" tickFormatter={v => `${v}ms`} tick={{ fontSize: 11 }} width={44} />
<YAxis yAxisId="errorRate" orientation="right" tickFormatter={v => `${v}%`} tick={{ fontSize: 11 }} width={36} />
<Tooltip />
<Line yAxisId="latency" type="monotone" dataKey="latency" stroke="#3b82f6" dot={false} strokeWidth={1.5} />
<Line yAxisId="errorRate" type="monotone" dataKey="errorRate" stroke="#ef4444" dot={false} strokeWidth={1.5} />
</LineChart>
</ResponsiveContainer>
)
}
For the Tremor UI alternative that wraps Recharts with pre-styled components (AreaChart, BarChart, DonutChart) designed for dashboards — less flexible but faster to set up with consistent styling, see the dashboard component patterns. For the Victory charting library alternative that provides a similar React component API with more animated transitions and React Native support through victory-native for cross-platform chart components, the data visualization guide covers Victory setup. The Claude Skills 360 bundle includes Recharts skill sets covering line/bar/area charts, custom tooltips, and real-time updates. Start with the free tier to try Recharts configuration generation.