EmpireUI
Get Pro
← Blog8 min read#react#recharts#data-visualization

Charts in React with Recharts: Line, Bar, Pie, Responsive

Recharts is the sanest way to add Line, Bar, and Pie charts to a React app — here's how to use it with real code, responsive containers, and Tailwind styling.

Colorful bar and line charts on a dark dashboard screen

Why Recharts Is the Default Chart Library for React

Honestly, most chart libraries feel like they were written for a different framework and then grudgingly ported to React. D3 is incredible but you're essentially learning a second language. Chart.js wraps a canvas element and requires imperative refs that fight React's rendering model. Recharts, on the other hand, is built from the ground up as a declarative React component tree — <LineChart>, <Bar>, <Tooltip> — which means it composes the same way the rest of your UI does.

Recharts sits on top of D3 internally, so the math is solid. But you never touch D3 directly unless you want to. The public API stays in JSX. As of Recharts v2.13.0, the library ships full TypeScript types, tree-shakes cleanly, and the bundle cost for a single chart type is around 60 kB gzipped — acceptable for most production apps.

The ecosystem fit is excellent too. Recharts respects React's reconciliation, so animated chart updates on data changes just work without manual plugin wiring. If you're already reaching for react-hook-form for your dashboard forms, Recharts slides in beside it without any compatibility headaches.

Installing Recharts and Setting Up TypeScript Types

Installation is a single command. Recharts v2.13.0 bundles its own TypeScript types so you don't need a separate @types package.

npm install recharts
# or
pnpm add recharts

If you're on a strict TypeScript project, add "moduleResolution": "bundler" to your tsconfig.json if it's not already there — some older configs set "node" which can miss Recharts' named exports. The types you'll use most often are TooltipProps, LegendProps, and the dataset shape interfaces like { name: string; value: number }. None of this requires extra packages. For tips on keeping TypeScript config lean in React projects, the react-typescript-tips guide covers the patterns you need.

Building a Responsive Line Chart

The ResponsiveContainer component is Recharts' answer to fluid layouts. Wrap any chart in it and it fills whatever parent element you put it in. Set width="100%" and a fixed height in pixels — that's usually the cleanest combination. Don't set both width and height as percentages or you'll get a zero-dimension container on first render.

Here's a complete line chart example with custom colors, a tooltip, and properly typed data:

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

const weeklyRevenue = [
  { day: 'Mon', revenue: 1200 },
  { day: 'Tue', revenue: 1850 },
  { day: 'Wed', revenue: 1430 },
  { day: 'Thu', revenue: 2200 },
  { day: 'Fri', revenue: 3100 },
  { day: 'Sat', revenue: 2750 },
  { day: 'Sun', revenue: 1900 },
];

export function RevenueLineChart() {
  return (
    <ResponsiveContainer width="100%" height={320}>
      <LineChart
        data={weeklyRevenue}
        margin={{ top: 8, right: 16, bottom: 0, left: 0 }}
      >
        <CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.08)" />
        <XAxis
          dataKey="day"
          tick={{ fill: '#94a3b8', fontSize: 12 }}
          axisLine={false}
          tickLine={false}
        />
        <YAxis
          tick={{ fill: '#94a3b8', fontSize: 12 }}
          axisLine={false}
          tickLine={false}
          tickFormatter={(v) => `$${v}`}
        />
        <Tooltip
          contentStyle={{
            background: 'rgba(15,23,42,0.9)',
            border: '1px solid rgba(255,255,255,0.1)',
            borderRadius: '8px',
            color: '#f1f5f9',
          }}
        />
        <Line
          type="monotone"
          dataKey="revenue"
          stroke="#818cf8"
          strokeWidth={2}
          dot={{ r: 4, fill: '#818cf8', strokeWidth: 0 }}
          activeDot={{ r: 6 }}
        />
      </LineChart>
    </ResponsiveContainer>
  );
}

A few things to notice. axisLine={false} and tickLine={false} on both axes is almost always what you want on a modern dashboard — the default axis lines look dated. The strokeDasharray="3 3" on CartesianGrid gives you a dashed grid that sits back visually. And the Tooltip contentStyle uses an inline object because Recharts doesn't read Tailwind classes on its floating elements — those render outside your normal CSS scope.

Bar Charts: Grouped, Stacked, and Horizontal

Bar charts follow almost the same structure as line charts. Swap LineChart for BarChart and Line for Bar. The radius prop on Bar — it accepts a four-value tuple [topLeft, topRight, bottomRight, bottomLeft] in pixels — is what gives you those rounded-top bars you see everywhere in 2026 dashboards.

For grouped bars (multiple data series side by side), add multiple <Bar> children. Recharts splits the available slot width automatically. Stacked bars need stackId="a" on each <Bar> that should share a stack — all bars with the same stackId string stack on top of each other.

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

const trafficData = [
  { month: 'Aug', organic: 4200, paid: 1800 },
  { month: 'Sep', organic: 5100, paid: 2200 },
  { month: 'Oct', organic: 6300, paid: 1900 },
  { month: 'Nov', organic: 7800, paid: 2600 },
];

export function TrafficBarChart() {
  return (
    <ResponsiveContainer width="100%" height={280}>
      <BarChart data={trafficData} barGap={4} barCategoryGap="24%">
        <XAxis dataKey="month" axisLine={false} tickLine={false}
          tick={{ fill: '#64748b', fontSize: 12 }} />
        <YAxis axisLine={false} tickLine={false}
          tick={{ fill: '#64748b', fontSize: 12 }} />
        <Tooltip
          cursor={{ fill: 'rgba(255,255,255,0.04)' }}
          contentStyle={{ background: '#1e293b', border: 'none', borderRadius: '6px' }}
        />
        <Bar dataKey="organic" fill="#6366f1" radius={[6, 6, 0, 0]} />
        <Bar dataKey="paid" fill="#f472b6" radius={[6, 6, 0, 0]} />
      </BarChart>
    </ResponsiveContainer>
  );
}

Horizontal bar charts are a layout="vertical" prop on BarChart plus swapped dataKey targets on XAxis (use type="number") and YAxis (use dataKey="name"). Useful when your category labels are long strings that won't fit on a narrow X axis.

Pie and Donut Charts with Custom Labels

Pie charts in Recharts are contained in <PieChart> with one or more <Pie> children. The donut variant is just a Pie with an innerRadius set — innerRadius={60} and outerRadius={90} is a typical ratio. You can render a centered label by absolutely positioning a <div> inside a relatively positioned wrapper, since Recharts uses SVG and doesn't provide a built-in center-slot component.

Custom label rendering via the label prop accepts a render function that receives the data for each segment. This is where you'd show percentages on each slice. But be honest with yourself — if you have more than six or seven slices, a pie chart is probably the wrong choice and a table or bar chart will communicate the data more clearly.

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

const breakdown = [
  { name: 'Direct', value: 38 },
  { name: 'Organic', value: 29 },
  { name: 'Social', value: 18 },
  { name: 'Referral', value: 15 },
];

const COLORS = ['#6366f1', '#22d3ee', '#f472b6', '#a3e635'];

export function TrafficDonut() {
  return (
    <div className="relative">
      <ResponsiveContainer width="100%" height={240}>
        <PieChart>
          <Pie
            data={breakdown}
            cx="50%"
            cy="50%"
            innerRadius={68}
            outerRadius={100}
            paddingAngle={3}
            dataKey="value"
          >
            {breakdown.map((_, index) => (
              <Cell
                key={`cell-${index}`}
                fill={COLORS[index % COLORS.length]}
                stroke="none"
              />
            ))}
          </Pie>
          <Tooltip
            formatter={(value: number) => [`${value}%`, '']}
            contentStyle={{
              background: '#0f172a',
              border: '1px solid rgba(255,255,255,0.08)',
              borderRadius: '8px',
            }}
          />
        </PieChart>
      </ResponsiveContainer>
      {/* Centered total label */}
      <div className="absolute inset-0 flex items-center justify-center">
        <span className="text-2xl font-bold text-white">100%</span>
      </div>
    </div>
  );
}

Theming Recharts to Match Your Tailwind Design System

Here's the thing: Recharts uses SVG for everything except the Tooltip and Legend, which are plain DOM elements. That split means you can't just add a Tailwind class to a <Line> and have it work — SVG attributes like stroke, fill, and fontSize are what actually control the look. For the Tooltip and Legend, though, you can pass className and Tailwind classes will apply.

The cleanest pattern for theming is to define a small color token object at the top of your chart files and reference it everywhere. Pull your Tailwind color values from CSS variables if you're using Tailwind v4.0.2's new @theme system — that keeps your chart colors in sync with the rest of the design system automatically. For dark mode, Recharts doesn't read prefers-color-scheme on its own, so you'll need to pass different color props based on your theme state. If you're using a theme toggle component, read the current theme from context and swap your chart color tokens accordingly.

One practical tip: the CartesianGrid default color #e0e0e0 is way too visible on dark backgrounds. Always override it — stroke="rgba(255,255,255,0.06)" is a good starting point for dark dashboards. And if you want toast notifications to appear when chart interactions happen (data point clicked, filter applied), react-toast-notifications integrates cleanly since it's just a React context provider sitting above your chart components.

Performance: Large Datasets and Re-Render Control

What happens when you throw 5,000 data points at a Recharts line chart? It renders them all — every point, every grid line, every tick. For datasets above a few hundred points you'll want to downsample before passing data into the chart. There's no built-in decimation in Recharts v2, so use a simple LTTB (Largest-Triangle-Three-Buckets) implementation or just slice to a maximum point count before the data hits the component.

Re-renders are the other footgun. Because Recharts components accept inline objects for props like margin, contentStyle, and dot, a new object reference is created on every parent render — which causes Recharts to re-animate the entire chart. Fix this with useMemo on your data arrays and by hoisting static config objects outside the component body rather than defining them inline in JSX.

Recharts also ships an isAnimationActive prop on every chart element. Set it to false during data streaming or when you're updating the chart more than a few times per second. The animation is beautiful for initial load but becomes visually noisy — and computationally expensive — on live-updating dashboards. If you want the full picture on React performance patterns, the react-performance-guide covers memoization, virtualization, and render profiling in depth.

Combining Recharts with Empire UI Components

Charts rarely live alone. They sit inside cards, modals, dashboard grids, and sidebars. Empire UI's free component library ships card, layout, and container components that pair directly with Recharts — you drop a <GlassCard> or dark panel component around your <ResponsiveContainer> and the chart inherits the surface styling automatically.

The 40 visual styles in Empire UI — glassmorphism, neumorphism, brutalism, clay, and the rest — each define a surface token that your chart container can pick up. A glassmorphism-styled chart card uses rgba(255,255,255,0.08) backgrounds with backdrop-filter: blur(12px) on the container while the chart SVG inside stays crisp because SVG content isn't affected by backdrop-filter. If you're curious what glassmorphism actually means as a design style, the what-is-glassmorphism guide is a good primer.

For full-page dashboards you'll likely want animated backgrounds behind your chart panels too. Empire UI's particles and aurora components work well here — they sit behind the card layer and give depth to the whole layout. The particles-background-react guide shows how to layer those without impacting chart render performance. The key is keeping the animation on a separate compositing layer (will-change: transform or a canvas element) so the browser doesn't repaint your chart SVG on every animation frame.

FAQ

Does Recharts work with Next.js App Router?

Yes, but Recharts components must run client-side because they use browser APIs and SVG DOM. Add 'use client' at the top of any file that imports from recharts. Don't put the directive in a layout or page server component — wrap your chart in a dedicated client component instead.

How do I make Recharts charts responsive to container width?

Wrap every chart in <ResponsiveContainer width="100%" height={N}> where N is a fixed pixel height. Avoid setting both dimensions as percentages — Recharts measures the parent's pixel dimensions and needs at least one absolute value to compute the SVG viewport correctly.

Can I use Tailwind CSS classes inside Recharts components?

Only on the Tooltip and Legend, which render as regular DOM elements and accept className. Chart primitives like Line, Bar, and Pie are SVG elements that use SVG presentation attributes (stroke, fill, fontSize) not CSS classes. Define your color values as JS constants and pass them directly as props.

How do I add a second Y axis in Recharts?

Add a second <YAxis> component with yAxisId="right" and orientation="right", then set yAxisId="right" on the <Line> or <Bar> that should use it. The first <YAxis> defaults to yAxisId="left". Make sure your primary chart elements also have explicit yAxisId props set or Recharts may warn about axis mismatches.

Why does my Recharts chart re-animate on every state update?

Recharts triggers re-animation when it detects new object references in props, even if the values are the same. Move margin, dot, and contentStyle objects outside the component body so they're stable references. Wrap data arrays in useMemo if they're derived from state. Setting isAnimationActive={false} on the chart element disables animation entirely if you need a quick fix.

What's the difference between Recharts v1 and v2?

Recharts v2 (released in 2022, now at v2.13.x) rewrote the internals with React hooks instead of class components, added bundled TypeScript types, improved tree-shaking, and fixed long-standing issues with ResponsiveContainer resize timing. If you're starting a new project, use v2. The API is mostly backwards-compatible but prop names for a few customization hooks changed.

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

Read next

Gantt Chart in React: Project Timeline without heavy libsSignature Pad in React: Canvas Drawing ComponentChart Dashboard in React: Recharts, Filters, Export to PNGReact UI Components Complete Reference: 60+ Patterns with Code