Skip to content
Dethink Components

ComponentsArea Chart

Area Chart

Volume over time on a single y-axis. Stack series to show how parts build a total, or overlap them to compare shapes. The crosshair, keyboard readout and table view work exactly as in Line Chart.

On this page

Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.

View registry files

Implemented with React and browser APIs, without an additional behavior or rendering library.

Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.

Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.

Import the component into your page or component file.

Usage
import { AreaChart } from "@dethink/components";

A stacked composition, two overlapping series, and a single series with gaps.

Stacked MRR by plan

The top edge reads as total MRR. The tooltip, readout and table list each plan's own value, not the cumulative one. Hiding a plan restacks the rest without repainting them.

MRR by plan, 2026

Area chart
MonthStarterGrowthEnterprise
Jan$8,200$21.4K$18K
Feb$8,600$22.1K$18K
Mar$9,100$23.8K$22.5K
Apr$9,300$24.6K$22.5K
May$9,900$25.2K$22.5K
Jun$10.4K$27.1K$26K
Jul$10.2K$28.4K$26K
Aug$10.9K$29K$30.5K
Sep$11.6K$31.2K$30.5K
Oct$11.8K$32.6K$30.5K
Nov$12.3K$33.1K$36K
Dec$12.9K$35.4K$36K

Series: Starter, Growth, Enterprise. 12 points from Jan to Dec. Use the arrow keys, Home and End to read values.

Show sourceexamples/area-chart/mrr.tsx
examples/area-chart/mrr.tsx
"use client";

import { AreaChart } from "@dethink/components";

const months = [
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec",
];
const starter = [
  8_200, 8_600, 9_100, 9_300, 9_900, 10_400, 10_200, 10_900, 11_600, 11_800,
  12_300, 12_900,
];
const growth = [
  21_400, 22_100, 23_800, 24_600, 25_200, 27_100, 28_400, 29_000, 31_200,
  32_600, 33_100, 35_400,
];
const enterprise = [
  18_000, 18_000, 22_500, 22_500, 22_500, 26_000, 26_000, 30_500, 30_500,
  30_500, 36_000, 36_000,
];

const data = months.map((month, i) => ({
  month,
  starter: starter[i],
  growth: growth[i],
  enterprise: enterprise[i],
}));

export function AreaChartMrr() {
  return (
    <div className="w-full">
      <h3 id="mrr-heading" className="mb-3 text-sm font-medium">
        MRR by plan, 2026
      </h3>
      <AreaChart
        aria-labelledby="mrr-heading"
        stacked
        data={data}
        index="month"
        indexLabel="Month"
        series={[
          { key: "starter", label: "Starter" },
          { key: "growth", label: "Growth" },
          { key: "enterprise", label: "Enterprise" },
        ]}
        formatOptions={{ style: "currency", currency: "USD" }}
      />
    </div>
  );
}

Overlapping series

Washes are drawn first and every line on top, so no fill hides another series' edge. Use two or three series at most when overlapping.

Sessions by device, this week

Area chart
DayDesktopMobile
Mon4,8202,310
Tue5,1402,450
Wed5,3902,620
Thu5,2102,580
Fri4,7602,940
Sat2,1303,710
Sun1,9803,880

Series: Desktop, Mobile. 7 points from Mon to Sun. Use the arrow keys, Home and End to read values.

Show sourceexamples/area-chart/sessions.tsx
examples/area-chart/sessions.tsx
"use client";

import { AreaChart } from "@dethink/components";

const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const desktop = [4_820, 5_140, 5_390, 5_210, 4_760, 2_130, 1_980];
const mobile = [2_310, 2_450, 2_620, 2_580, 2_940, 3_710, 3_880];

const data = days.map((day, i) => ({
  day,
  desktop: desktop[i],
  mobile: mobile[i],
}));

export function AreaChartSessions() {
  return (
    <div className="w-full">
      <h3 id="sessions-heading" className="mb-3 text-sm font-medium">
        Sessions by device, this week
      </h3>
      <AreaChart
        aria-labelledby="sessions-heading"
        data={data}
        index="day"
        indexLabel="Day"
        height={240}
        series={[
          { key: "desktop", label: "Desktop" },
          { key: "mobile", label: "Mobile" },
        ]}
      />
    </div>
  );
}

Single series with gaps

One series needs no legend. Missing readings leave a gap in both the wash and the line.

Daily active users

Area chart
DayActive users
Sep 11,210
Sep 21,260
Sep 31,190
Sep 41,320
Sep 51,410
Sep 61,380
Sep 7—
Sep 8—
Sep 91,520
Sep 101,610
Sep 111,580
Sep 121,690
Sep 131,740
Sep 141,720

Series: Active users. 14 points from Sep 1 to Sep 14. Use the arrow keys, Home and End to read values.

Show sourceexamples/area-chart/active-users.tsx
examples/area-chart/active-users.tsx
"use client";

import { AreaChart } from "@dethink/components";

const values = [
  1_210,
  1_260,
  1_190,
  1_320,
  1_410,
  1_380,
  null,
  null,
  1_520,
  1_610,
  1_580,
  1_690,
  1_740,
  1_720,
];

const data = values.map((users, i) => ({ day: `Sep ${i + 1}`, users }));

export function AreaChartActiveUsers() {
  return (
    <div className="w-full">
      <h3 id="active-users-heading" className="mb-3 text-sm font-medium">
        Daily active users
      </h3>
      <AreaChart
        aria-labelledby="active-users-heading"
        data={data}
        index="day"
        indexLabel="Day"
        height={200}
        curve="linear"
        series={[{ key: "users", label: "Active users", color: "chart-3" }]}
      />
    </div>
  );
}
AreaChart props
PropWhat it doesDefault
dataArray<Record<string, unknown>>One record per x position, in order. Non-numeric values leave a gap.Not set
indexstringRecord key holding the x value, such as "month".Not set
seriesArray<{ key, label, color? }>Areas on one y-axis. color is a palette slot or CSS color; it defaults to the slot for the series' position, so filtering never repaints survivors.Not set
stackedbooleanStack series so the top edge reads as the total. Positive values stack up, negatives down, and a missing value stacks as zero. Tooltip, readout and table keep each series' own value.false
curve"monotone" | "linear" | "step"Monotone never overshoots the data."monotone"
heightnumber | stringPlot height. Width always fills the container.288
yDomain / includeZero[number, number] / booleanFix the value domain, or let the niced data extent drop zero.— / true
formatValue / formatOptions(value) => string / Intl.NumberFormatOptions & { locale? }Formats axis ticks, tooltip, readout and table values.Compact en-US
formatIndex / indexLabel(value, index) => string / stringLabels x values and names the table's first column.String(value) / index
legendbooleanToggle buttons that show or hide series; hovering one dims the others.series.length >= 2
hiddenSeries / defaultHiddenSeries / onHiddenSeriesChangestring[] / string[] / (hidden) => voidControlled or uncontrolled series filtering. The last visible series cannot be hidden.— / [] / —
showTable / defaultShowTable / onShowTableChangeboolean / boolean / (show) => voidSwap the plot for a visible data table. The table is always present for assistive technology.— / false / —
tableTogglebooleanRender the Table view toggle button.true
loading / loadingLabel / emptyLabelboolean / ReactNode / ReactNodeRefetches keep the previous render at reduced opacity.false / "Loading…" / "No data"
animatebooleanDraw-in and wash fade on mount. Always off under reduced motion.true

AreaChart shares Line Chart's accessible shell. The plot is a single tab stop named by aria-label or aria-labelledby. Arrow keys, Home and End move the crosshair, Escape clears it, and each move is announced through a polite live region listing every series' own value, stacked or not. The data table is always present for assistive technology and can be shown with Table view. Washes are decorative; the legend, tooltip and table name every series, so identity never relies on color alone.