React Charts with Recharts: Line, Bar, Area, Real-Time Data, and Accessible Visualizations
Build production data visualizations in React with Recharts. Covers responsive line charts with real data, stacked bar charts, area charts with gradients, real-time streaming charts, custom tooltips, and ARIA accessibility for charts.
Recharts is the most mature React charting library in 2027 โ it's built on SVG, has excellent TypeScript types, renders server-side (no window errors in Next.js), and requires no D3 knowledge. The main alternatives (Victory, Chart.js, Visx) are either less composable or have worse SSR stories.
This guide covers the practical patterns for dashboards: line charts for time series, bar charts for comparisons, area charts for cumulative metrics, and real-time streaming charts.
Installation
npm install recharts
Responsive Line Chart (DAU)
// components/charts/dau-chart.tsx
"use client";
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, Legend,
} from "recharts";
import { format, parseISO } from "date-fns";
interface DAUDataPoint {
date: string; // "2027-05-21"
dau: number;
}
interface DAUChartProps {
data: DAUDataPoint[];
height?: number;
}
function CustomTooltip({
active, payload, label,
}: {
active?: boolean;
payload?: Array<{ value: number; name: string; color: string }>;
label?: string;
}) {
if (!active || !payload?.length) return null;
return (
<div className="bg-white border border-gray-200 rounded-xl shadow-lg px-4 py-3">
<p className="text-xs text-gray-500 mb-2">
{label ? format(parseISO(label), "MMM d, yyyy") : ""}
</p>
{payload.map((entry) => (
<div key={entry.name} className="flex items-center gap-2 text-sm">
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: entry.color }} />
<span className="text-gray-600">{entry.name}:</span>
<span className="font-semibold text-gray-900">{entry.value.toLocaleString()}</span>
</div>
))}
</div>
);
}
export function DAUChart({ data, height = 300 }: DAUChartProps) {
return (
<div
role="img"
aria-label={`Daily active users over the last ${data.length} days. Peak: ${Math.max(...data.map((d) => d.dau)).toLocaleString()} users.`}
>
<ResponsiveContainer width="100%" height={height}>
<LineChart
data={data}
margin={{ top: 5, right: 20, left: 0, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" vertical={false} />
<XAxis
dataKey="date"
tickFormatter={(value) => format(parseISO(value), "MMM d")}
tick={{ fontSize: 12, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
interval="preserveStartEnd"
/>
<YAxis
tick={{ fontSize: 12, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
tickFormatter={(v) => v >= 1000 ? `${(v / 1000).toFixed(1)}k` : v}
width={40}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="dau"
name="Daily Active Users"
stroke="#2563eb"
strokeWidth={2}
dot={false}
activeDot={{ r: 4, strokeWidth: 0 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
);
}
๐ Looking for a Dev Team That Actually Delivers?
Most agencies sell you a project manager and assign juniors. Viprasol is different โ senior engineers only, direct Slack access, and a 5.0โ Upwork record across 100+ projects.
- React, Next.js, Node.js, TypeScript โ production-grade stack
- Fixed-price contracts โ no surprise invoices
- Full source code ownership from day one
- 90-day post-launch support included
Stacked Bar Chart (Revenue by Plan)
// components/charts/revenue-by-plan-chart.tsx
"use client";
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
Legend, ResponsiveContainer, Cell,
} from "recharts";
import { format, parseISO } from "date-fns";
interface RevenueDataPoint {
month: string;
free: number;
starter: number;
growth: number;
enterprise: number;
}
const PLAN_COLORS = {
free: "#e5e7eb",
starter: "#93c5fd",
growth: "#3b82f6",
enterprise: "#1d4ed8",
};
const PLAN_LABELS = {
free: "Free",
starter: "Starter",
growth: "Growth",
enterprise: "Enterprise",
};
function formatCurrency(cents: number): string {
return `$${(cents / 100).toLocaleString()}`;
}
export function RevenueByPlanChart({ data }: { data: RevenueDataPoint[] }) {
return (
<div role="img" aria-label="Monthly recurring revenue by plan tier">
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" vertical={false} />
<XAxis
dataKey="month"
tickFormatter={(v) => format(parseISO(v + "-01"), "MMM yy")}
tick={{ fontSize: 12, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
/>
<YAxis
tickFormatter={formatCurrency}
tick={{ fontSize: 12, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
width={70}
/>
<Tooltip
formatter={(value: number, name: string) => [
formatCurrency(value),
PLAN_LABELS[name as keyof typeof PLAN_LABELS],
]}
labelFormatter={(label) => format(parseISO(label + "-01"), "MMMM yyyy")}
contentStyle={{ border: "1px solid #e5e7eb", borderRadius: "12px" }}
/>
<Legend
formatter={(value) => PLAN_LABELS[value as keyof typeof PLAN_LABELS]}
/>
{(["free", "starter", "growth", "enterprise"] as const).map((plan) => (
<Bar
key={plan}
dataKey={plan}
stackId="revenue"
fill={PLAN_COLORS[plan]}
radius={plan === "enterprise" ? [4, 4, 0, 0] : [0, 0, 0, 0]}
maxBarSize={60}
/>
))}
</BarChart>
</ResponsiveContainer>
</div>
);
}
Area Chart with Gradient (Cumulative MRR)
// components/charts/mrr-chart.tsx
"use client";
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, defs, linearGradient, stop,
} from "recharts";
import { format, parseISO } from "date-fns";
export function MRRChart({ data }: { data: Array<{ date: string; mrr: number }> }) {
return (
<div role="img" aria-label="Monthly recurring revenue growth over time">
<ResponsiveContainer width="100%" height={250}>
<AreaChart data={data} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
{/* Gradient fill definition */}
<defs>
<linearGradient id="mrrGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#2563eb" stopOpacity={0.2} />
<stop offset="95%" stopColor="#2563eb" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" vertical={false} />
<XAxis
dataKey="date"
tickFormatter={(v) => format(parseISO(v), "MMM")}
tick={{ fontSize: 12, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
/>
<YAxis
tickFormatter={(v) => `$${(v / 100 / 1000).toFixed(0)}k`}
tick={{ fontSize: 12, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
width={45}
/>
<Tooltip
formatter={(value: number) => [`$${(value / 100).toLocaleString()}`, "MRR"]}
labelFormatter={(label) => format(parseISO(label), "MMMM yyyy")}
contentStyle={{ border: "1px solid #e5e7eb", borderRadius: "12px" }}
/>
<Area
type="monotone"
dataKey="mrr"
stroke="#2563eb"
strokeWidth={2}
fill="url(#mrrGradient)"
dot={false}
activeDot={{ r: 4, strokeWidth: 0, fill: "#2563eb" }}
/>
</AreaChart>
</ResponsiveContainer>
</div>
);
}
๐ Senior Engineers. No Junior Handoffs. Ever.
You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.
- MVPs in 4โ8 weeks, full platforms in 3โ5 months
- Lighthouse 90+ performance scores standard
- Works across US, UK, AU timezones
- Free 30-min architecture review, no commitment
Real-Time Chart (Streaming Updates)
// components/charts/realtime-chart.tsx
"use client";
import { useState, useEffect, useRef } from "react";
import { LineChart, Line, XAxis, YAxis, ResponsiveContainer, Tooltip } from "recharts";
import { format } from "date-fns";
interface DataPoint {
time: number; // Unix timestamp ms
value: number;
}
const MAX_POINTS = 60; // Keep last 60 data points
interface RealtimeChartProps {
dataStream: EventSource | null; // SSE stream
label: string;
color?: string;
}
export function RealtimeChart({ dataStream, label, color = "#2563eb" }: RealtimeChartProps) {
const [data, setData] = useState<DataPoint[]>([]);
useEffect(() => {
if (!dataStream) return;
function handleMessage(event: MessageEvent) {
const parsed = JSON.parse(event.data) as { value: number };
setData((prev) => {
const next = [...prev, { time: Date.now(), value: parsed.value }];
return next.slice(-MAX_POINTS); // Keep rolling window
});
}
dataStream.addEventListener("metric", handleMessage);
return () => dataStream.removeEventListener("metric", handleMessage);
}, [dataStream]);
return (
<div role="img" aria-label={`Real-time ${label} chart`}>
<ResponsiveContainer width="100%" height={200}>
<LineChart data={data} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
<XAxis
dataKey="time"
type="number"
scale="time"
domain={["dataMin", "dataMax"]}
tickFormatter={(t) => format(new Date(t), "HH:mm:ss")}
tick={{ fontSize: 11, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
interval="preserveStartEnd"
/>
<YAxis
tick={{ fontSize: 11, fill: "#6b7280" }}
axisLine={false}
tickLine={false}
width={35}
/>
<Tooltip
labelFormatter={(t) => format(new Date(t as number), "HH:mm:ss")}
formatter={(v: number) => [v.toFixed(2), label]}
contentStyle={{ border: "1px solid #e5e7eb", borderRadius: "8px", fontSize: "12px" }}
/>
<Line
type="linear"
dataKey="value"
name={label}
stroke={color}
strokeWidth={1.5}
dot={false}
isAnimationActive={false} // Disable animation for real-time (causes flicker)
/>
</LineChart>
</ResponsiveContainer>
</div>
);
}
Dashboard Chart Grid
// components/dashboard/metrics-charts.tsx
"use client";
import { DAUChart } from "@/components/charts/dau-chart";
import { MRRChart } from "@/components/charts/mrr-chart";
import { RevenueByPlanChart } from "@/components/charts/revenue-by-plan-chart";
interface MetricsChartsProps {
dauData: Array<{ date: string; dau: number }>;
mrrData: Array<{ date: string; mrr: number }>;
revenueData: Array<{ month: string; free: number; starter: number; growth: number; enterprise: number }>;
}
export function MetricsCharts({ dauData, mrrData, revenueData }: MetricsChartsProps) {
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white border border-gray-200 rounded-2xl p-5">
<h3 className="text-sm font-semibold text-gray-700 mb-4">Daily Active Users</h3>
<DAUChart data={dauData} />
</div>
<div className="bg-white border border-gray-200 rounded-2xl p-5">
<h3 className="text-sm font-semibold text-gray-700 mb-4">MRR Growth</h3>
<MRRChart data={mrrData} />
</div>
<div className="bg-white border border-gray-200 rounded-2xl p-5 lg:col-span-2">
<h3 className="text-sm font-semibold text-gray-700 mb-4">Revenue by Plan</h3>
<RevenueByPlanChart data={revenueData} />
</div>
</div>
);
}
Cost and Timeline Estimates
| Scope | Team | Timeline | Cost Range |
|---|---|---|---|
| 2โ3 basic charts (line, bar, area) | 1 dev | 1โ2 days | $400โ800 |
| Custom tooltips + responsive + accessible | 1 dev | 1 day | $300โ600 |
| Real-time streaming chart + SSE | 1 dev | 1โ2 days | $400โ800 |
| Full analytics dashboard (6+ charts) | 1โ2 devs | 1 week | $3,000โ5,000 |
See Also
- SaaS Usage Analytics
- React Data Visualization (Recharts)
- Next.js Streaming Responses
- PostgreSQL Window Functions
- React Suspense Patterns
Working With Viprasol
Charts look simple but break in several ways: missing isAnimationActive={false} in real-time charts causes flickering, missing role="img" and aria-label fails accessibility audits, and missing ResponsiveContainer means fixed-width charts on mobile. Our team builds Recharts dashboards with custom tooltips, gradient fills, real-time SSE streaming (without animation), and ARIA attributes for screen readers.
What we deliver:
DAUChart: LineChart + custom tooltip with date-fns format + Y-axiskabbreviationRevenueByPlanChart: stacked BarChart with plan color map, Legend formatter, radius on top barMRRChart: AreaChart withlinearGradientdefs, gradient fill, currency formattingRealtimeChart: SSE EventSource listener, rolling 60-point window,isAnimationActive={false}, time X-axis- All charts:
ResponsiveContainer,role="img",aria-labelwith summary stats
Talk to our team about your analytics dashboard โ
Or explore our web development services.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need a Modern Web Application?
From landing pages to complex SaaS platforms โ we build it all with Next.js and React.
Free consultation โข No commitment โข Response within 24 hours
Need a custom web application built?
We build React and Next.js web applications with Lighthouse โฅ90 scores, mobile-first design, and full source code ownership. Senior engineers only โ from architecture through deployment.