Back to Blog

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.

Viprasol Tech Team
June 3, 2027
12 min read

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

ScopeTeamTimelineCost Range
2โ€“3 basic charts (line, bar, area)1 dev1โ€“2 days$400โ€“800
Custom tooltips + responsive + accessible1 dev1 day$300โ€“600
Real-time streaming chart + SSE1 dev1โ€“2 days$400โ€“800
Full analytics dashboard (6+ charts)1โ€“2 devs1 week$3,000โ€“5,000

See Also


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-axis k abbreviation
  • RevenueByPlanChart: stacked BarChart with plan color map, Legend formatter, radius on top bar
  • MRRChart: AreaChart with linearGradient defs, gradient fill, currency formatting
  • RealtimeChart: SSE EventSource listener, rolling 60-point window, isAnimationActive={false}, time X-axis
  • All charts: ResponsiveContainer, role="img", aria-label with summary stats

Talk to our team about your analytics dashboard โ†’

Or explore our web development services.

Share this article:

About the Author

V

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.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

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

Viprasol ยท Web Development

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.