EmpireUI
Get Pro
← Blog8 min read#recharts#chart#react

Charts in React with Recharts: Bar, Line, Area and Pie in 2026

Ship bar, line, area and pie charts in React using Recharts 2.x — real code, responsive containers, custom tooltips, and honest takes on when not to use it.

colorful data visualization charts on dark abstract background

Why Recharts Is Still the Default Pick in 2026

There are a dozen charting libraries for React. D3-based wrappers, canvas renderers, declarative SVG systems — you've probably bookmarked half of them. Recharts keeps winning the install count game not because it's the most feature-rich (it isn't) but because its declarative component API maps almost perfectly to how React developers already think. You compose charts the same way you compose UI.

Honestly, if you need a bar chart that updates on state change, Recharts gets you there in about 20 lines. No manual SVG surgery, no D3 selection gymnastics. The library sits at version 2.12 as of mid-2026 and the API has been stable for years — a rare thing in the JS ecosystem.

That said, Recharts has real limits. Animation performance above 2000+ data points starts to hurt. If you need real-time streaming at 60fps or financial-grade candlestick charts, look at uPlot or Lightweight Charts instead. For the bread-and-butter dashboard use case though — this is your library.

Worth noting: Recharts is SVG-based, which means full CSS theming and accessible screen-reader attributes with zero extra work. That matters more than people give it credit for, especially when your design system uses CSS custom properties for color tokens.

Installation and Project Setup

One command, no build config changes required. Recharts ships its own types so there's no separate @types/recharts package to chase down.

npm install recharts
# or
pnpm add recharts

The only real gotcha is that Recharts requires React 18+ for concurrent mode compatibility and expects your bundler to handle ESM correctly. If you're on Vite 5 or Next.js 14+, you're fine out of the box. CRA users still on webpack 4 may hit a peer dep warning — just ignore it or pin recharts@2.10 to dodge it.

// Verify your import works correctly
import {
  BarChart, LineChart, AreaChart, PieChart,
  Bar, Line, Area, Pie,
  XAxis, YAxis, CartesianGrid, Tooltip, Legend,
  ResponsiveContainer, Cell
} from 'recharts';

Everything you need lives in that single named-export list. No sub-packages, no tree-shaking gotchas.

Bar Charts: The Workhorse

Bar charts are probably the first thing your PM is going to ask for. Monthly revenue, user signups, category comparisons — bar chart, every time. Recharts makes this embarrassingly simple, and ResponsiveContainer handles the responsive sizing you'd otherwise wire up manually.

import {
  BarChart, Bar, XAxis, YAxis, CartesianGrid,
  Tooltip, Legend, ResponsiveContainer
} from 'recharts';

const data = [
  { month: 'Jan', revenue: 4200, expenses: 2400 },
  { month: 'Feb', revenue: 5800, expenses: 2900 },
  { month: 'Mar', revenue: 4700, expenses: 2100 },
  { month: 'Apr', revenue: 7300, expenses: 3100 },
  { month: 'May', revenue: 6900, expenses: 2800 },
  { month: 'Jun', revenue: 8200, expenses: 3400 },
];

export function RevenueBarChart() {
  return (
    <ResponsiveContainer width="100%" height={320}>
      <BarChart data={data} margin={{ top: 8, right: 24, left: 0, bottom: 0 }}>
        <CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.1)" />
        <XAxis dataKey="month" tick={{ fill: '#94a3b8', fontSize: 12 }} />
        <YAxis tick={{ fill: '#94a3b8', fontSize: 12 }} />
        <Tooltip
          contentStyle={{
            background: 'rgba(15,15,15,0.9)',
            border: '1px solid rgba(255,255,255,0.1)',
            borderRadius: 8,
          }}
        />
        <Legend />
        <Bar dataKey="revenue" fill="#6366f1" radius={[4, 4, 0, 0]} />
        <Bar dataKey="expenses" fill="#f43f5e" radius={[4, 4, 0, 0]} />
      </BarChart>
    </ResponsiveContainer>
  );
}

The radius prop on Bar takes a 4-tuple [topLeft, topRight, bottomRight, bottomLeft] — just like CSS border-radius. Setting [4, 4, 0, 0] gives you the rounded-top style that's been everywhere in dashboards since 2022. Small detail, big visual upgrade.

In practice, the margin prop on BarChart trips people up more than anything else. If your Y axis labels are clipping on the left edge, bump margin.left to 16 or 24. The chart doesn't auto-compute space for axis labels — you own that number.

Line and Area Charts: Trend Data Done Right

Line charts are for time-series. Area charts are for time-series where the filled region communicates magnitude. Pick based on what you're trying to say, not visual preference.

import {
  LineChart, Line, AreaChart, Area,
  XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer
} from 'recharts';

const sessionData = [
  { day: 'Mon', sessions: 1200, pageviews: 3800 },
  { day: 'Tue', sessions: 1900, pageviews: 5200 },
  { day: 'Wed', sessions: 1500, pageviews: 4100 },
  { day: 'Thu', sessions: 2300, pageviews: 6700 },
  { day: 'Fri', sessions: 2100, pageviews: 5900 },
  { day: 'Sat', sessions: 800,  pageviews: 2200 },
  { day: 'Sun', sessions: 600,  pageviews: 1800 },
];

// Smooth gradient area chart
export function SessionAreaChart() {
  return (
    <ResponsiveContainer width="100%" height={280}>
      <AreaChart data={sessionData}>
        <defs>
          <linearGradient id="colorSessions" x1="0" y1="0" x2="0" y2="1">
            <stop offset="5%" stopColor="#6366f1" stopOpacity={0.4} />
            <stop offset="95%" stopColor="#6366f1" stopOpacity={0} />
          </linearGradient>
        </defs>
        <CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.08)" />
        <XAxis dataKey="day" tick={{ fill: '#64748b', fontSize: 11 }} />
        <YAxis tick={{ fill: '#64748b', fontSize: 11 }} />
        <Tooltip />
        <Area
          type="monotone"
          dataKey="sessions"
          stroke="#6366f1"
          strokeWidth={2}
          fill="url(#colorSessions)"
        />
      </AreaChart>
    </ResponsiveContainer>
  );
}

The linearGradient SVG element inside <defs> is the trick that turns a flat area chart into something worth putting on a landing page. Reference it via fill="url(#colorSessions)". Every gradient needs a unique id — if you render multiple area charts on the same page and reuse the same id, they'll all steal the first gradient definition they find in the DOM. Ask me how I know.

The type="monotone" prop on Line and Area controls curve interpolation. monotone prevents the line from overshooting data points — you want this for real data. natural gives you a more organic curve that looks pretty but can misrepresent values. linear is perfectly straight segments. Choose monotone by default and only deviate when you have a design reason.

One more thing — if you want a dot-free line (common in sparkline use cases), just pass dot={false} to the Line component. And activeDot={{ r: 6, strokeWidth: 2 }} gives you a larger hit target on hover for accessibility.

Pie Charts: Composition Data

Pie charts get a bad rap from data-viz purists, and they're not wrong that bar charts communicate proportional differences more accurately. But pie charts are fine for 3-5 categories where rough proportions matter and the absolute values are secondary. Think "traffic by source" or "revenue by plan tier."

import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts';

const planData = [
  { name: 'Free',     value: 5400 },
  { name: 'Pro',      value: 2100 },
  { name: 'Business', value: 980 },
  { name: 'Lifetime', value: 320 },
];

const COLORS = ['#6366f1', '#8b5cf6', '#a78bfa', '#c4b5fd'];

export function PlanPieChart() {
  return (
    <ResponsiveContainer width="100%" height={300}>
      <PieChart>
        <Pie
          data={planData}
          cx="50%"
          cy="50%"
          innerRadius={70}
          outerRadius={110}
          paddingAngle={3}
          dataKey="value"
        >
          {planData.map((_, index) => (
            <Cell key={index} fill={COLORS[index % COLORS.length]} />
          ))}
        </Pie>
        <Tooltip
          contentStyle={{
            background: '#0f0f0f',
            border: '1px solid #27272a',
            borderRadius: 8,
          }}
        />
        <Legend />
      </PieChart>
    </ResponsiveContainer>
  );
}

Setting innerRadius creates a donut chart — which is almost always the better choice over a filled pie. The central empty space lets you display a total or label without cluttering the arcs. paddingAngle={3} adds 3px of gap between segments, which reads much cleaner than segments bleeding into each other.

Quick aside: cx and cy accept both pixel values and percentages. Stick with 50% unless you're building a custom layout where the pie is intentionally off-center.

The Cell component is how you assign per-segment colors. Map over your data array and assign colors from a palette array using modulo. If you need to highlight a specific segment on hover, use the activeIndex state pattern with onMouseEnter/onMouseLeave callbacks on the Pie component.

Custom Tooltips and Dark-Mode Theming

The default Recharts tooltip is ugly. Plain white box, tiny text, no brand alignment. But the content prop on <Tooltip> accepts a render function that gives you complete control — same pattern as any headless UI library.

const CustomTooltip = ({ active, payload, label }: any) => {
  if (!active || !payload?.length) return null;

  return (
    <div
      style={{
        background: 'rgba(9, 9, 11, 0.95)',
        border: '1px solid rgba(99, 102, 241, 0.3)',
        borderRadius: 10,
        padding: '10px 16px',
        backdropFilter: 'blur(8px)',
      }}
    >
      <p style={{ color: '#94a3b8', fontSize: 11, marginBottom: 6 }}>{label}</p>
      {payload.map((entry: any) => (
        <p key={entry.name} style={{ color: entry.color, fontSize: 13 }}>
          {entry.name}: <strong>{entry.value.toLocaleString()}</strong>
        </p>
      ))}
    </div>
  );
};

// Then on your chart:
<Tooltip content={<CustomTooltip />} />

That backdropFilter: blur(8px) on the tooltip is a trick worth stealing — it makes your tooltip feel like a glassmorphism overlay, which pairs perfectly with dark dashboard UIs. If your app already uses Empire UI's glassmorphism components, this tooltip style matches out of the box.

For dark-mode theming more broadly, Recharts doesn't have a built-in theme system. You thread colors through props. The most maintainable pattern is to define a palette object keyed to your CSS custom properties and reference it across all charts in a central chartConfig.ts file. One file to update when your brand colors change.

Look, a lot of developers waste time trying to override Recharts internal styles with CSS selectors like .recharts-tooltip-wrapper. Don't. The component API gives you enough surface area to do everything you need without fighting the DOM.

Integrating Charts Into Your Dashboard UI

Charts rarely live in isolation — they're embedded in dashboard cards, wrapped in tabs, filtered by date pickers. How you integrate matters as much as the chart configuration itself.

Wrap every chart in ResponsiveContainer with width="100%" and a fixed height. Never use a fixed width — it breaks responsive layouts immediately. If your chart card has padding, account for it; a 320px tall card with 24px padding on all sides gives you 272px of chart height. That 48px difference sounds minor until your Y-axis labels start colliding.

For dashboards built with Empire UI, the clean approach is to put each chart inside a card component from the component library — the dark card variants handle the border, shadow, and background so your chart code stays purely about data. Want a premium glassmorphism finish on those cards? Check out the glassmorphism generator to dial in your exact backdrop-filter and opacity values before committing to code.

// Example: chart inside a simple dashboard card
export function MetricCard({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <div className="rounded-2xl border border-white/10 bg-white/5 backdrop-blur-md p-6">
      <h3 className="text-sm font-medium text-slate-400 mb-4">{title}</h3>
      {children}
    </div>
  );
}

// Usage
<MetricCard title="Monthly Revenue">
  <RevenueBarChart />
</MetricCard>

One more thing — if you're rendering many charts on a single page and noticing layout shifts on load, the issue is usually ResponsiveContainer measuring its parent before styles have applied. Fix it by giving the parent container an explicit height via Tailwind (h-80) or a CSS class, not just relying on content height. The chart renders synchronously after the measurement, so flicker disappears.

FAQ

Is Recharts still actively maintained in 2026?

Yes — version 2.12 shipped in early 2026 with improved TypeScript types and React 18 concurrent mode fixes. The API is stable and breaking changes are rare.

How do I make Recharts charts responsive?

Wrap your chart component in <ResponsiveContainer width="100%" height={320}>. It observes the parent container's width and redraws automatically — just make sure the parent has a defined width, not a width derived solely from its children.

Can I use Recharts with Next.js App Router?

Yes, but Recharts uses browser APIs so it must run client-side. Add 'use client' at the top of any component file that imports from recharts. There's no SSR-compatible rendering mode.

What's the difference between LineChart and AreaChart in Recharts?

AreaChart is a LineChart with the region between the line and the X-axis filled in. Use AreaChart when the shaded area communicates volume or magnitude; use LineChart when you want to compare trends without the visual weight of fills.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Chart Dashboard in React: Recharts, Filters, Export to PNGHero Section Design: 8 Layouts With Full React + Tailwind CodeGlassmorphism Charts in React: Recharts With Frosted Glass StyleCharts in React with Recharts: Line, Bar, Pie, Responsive