# Area Chart

Show how one or several measures change over time as filled areas, overlaid or stacked.

Page views by month

[Complete TSX file](/examples/area-chart/default.tsx)

```tsx
"use client";

import { AreaChart } from "@/siteplane/components/area-chart";

const pageViews = [
  { month: "Jan", views: 18200 },
  { month: "Feb", views: 21400 },
  { month: "Mar", views: 19800 },
  { month: "Apr", views: 24600 },
  { month: "May", views: 27300 },
  { month: "Jun", views: 25900 },
  { month: "Jul", views: 29800 },
  { month: "Aug", views: 31200 },
  { month: "Sep", views: 28700 },
  { month: "Oct", views: 33500 },
  { month: "Nov", views: 36100 },
  { month: "Dec", views: 38900 },
];

export default function Example() {
  return (
    <div className="w-full max-w-xl">
      <AreaChart
        data={pageViews}
        label="Page views by month"
        series={[{ key: "views", label: "Page views" }]}
        x="month"
      />
    </div>
  );
}
```

An area chart shows how one or several measures change over time, with the space between each line and 0 filled, so the size of a measure reads at a glance. Stack the areas to show how parts add up to a total. Hover the plot to read every series at the nearest point, or focus it and step through the points with the arrow keys; the card trails beside the crosshair.

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/area-chart
```

With npm: `npx shadcn@4.18.0 add @siteplane/area-chart`. [Registry JSON](/registry/area-chart.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/card @siteplane/toggle-group
```

## Usage

```tsx
import { AreaChart } from "@/siteplane/components/area-chart";

<AreaChart
  label="Page views by month"
  data={[
    { month: "Jan", views: 18200 },
    { month: "Feb", views: 21400 },
    { month: "Mar", views: 19800 },
  ]}
  x="month"
  series={[{ key: "views", label: "Page views" }]}
/>;
```

`x` names the key of the x value in each row, and each series names the key of its numbers.

## Stacked

Set `stacked` to stack the areas in the order of `series`, the first at the bottom, so the top of the stack shows the total. The tooltip shows each series’ own value, not the running sum. A series that names no `color` takes a shade of the brand color, from strong to light, spread over the number of series.

Visitors by source, stacked

[Complete TSX file](/examples/area-chart/stacked.tsx)

```tsx
"use client";

import { AreaChart } from "@/siteplane/components/area-chart";

const visitors = [
  { month: "Jan", direct: 2600, referral: 900, search: 4200 },
  { month: "Feb", direct: 2750, referral: 1040, search: 4650 },
  { month: "Mar", direct: 2690, referral: 1210, search: 4480 },
  { month: "Apr", direct: 2980, referral: 1180, search: 5120 },
  { month: "May", direct: 3150, referral: 1420, search: 5490 },
  { month: "Jun", direct: 3320, referral: 1610, search: 5860 },
];

export default function Example() {
  return (
    <div className="w-full max-w-xl">
      <AreaChart
        data={visitors}
        label="Visitors by source and month"
        series={[
          { key: "search", label: "Search" },
          { key: "direct", label: "Direct" },
          { key: "referral", label: "Referral" },
        ]}
        stacked
        x="month"
      />
    </div>
  );
}
```

## Fill

`fill` sets how an area is filled: the default `gradient` fades the series color out toward the base of the area, downward above 0 and upward below it, and `solid` fills it with a flat tint. Either way, a line runs along the top of each area.

The gradient and the solid fill

[Complete TSX file](/examples/area-chart/fill.tsx)

```tsx
"use client";

import { AreaChart } from "@/siteplane/components/area-chart";

const downloads = [
  { month: "Jan", downloads: 3240 },
  { month: "Feb", downloads: 3980 },
  { month: "Mar", downloads: 3610 },
  { month: "Apr", downloads: 4470 },
  { month: "May", downloads: 5120 },
  { month: "Jun", downloads: 4860 },
];

const fills = ["gradient", "solid"] as const;

export default function Example() {
  return (
    <div className="grid w-full max-w-xl gap-x-8 gap-y-6 sm:grid-cols-2">
      {fills.map((fill) => (
        <div className="grid min-w-0 gap-2" key={fill}>
          <p className="text-xs text-muted-foreground">
            {fill === "gradient" ? "gradient (default)" : fill}
          </p>
          <AreaChart
            data={downloads}
            fill={fill}
            height={200}
            label={`Downloads by month, ${fill} fill`}
            series={[{ key: "downloads", label: "Downloads" }]}
            x="month"
          />
        </div>
      ))}
    </div>
  );
}
```

## Curves

`curve` takes the same four curves as the [Line Chart](/docs/components/line-chart/#curves): `monotone`, `smooth`, `linear` and `step`. `monotone`, the default, never swings past the data, so an area that rests on 0, such as build minutes that stop between busy hours, stays on 0. `smooth` is a natural spline that bends evenly and can swing a little past the data between two points.

Build minutes by hour on a monotone curve

[Complete TSX file](/examples/area-chart/curves.tsx)

```tsx
"use client";

import { AreaChart } from "@/siteplane/components/area-chart";

const buildMinutes = [
  { hour: "8 AM", minutes: 0 },
  { hour: "9 AM", minutes: 0 },
  { hour: "10 AM", minutes: 40 },
  { hour: "11 AM", minutes: 85 },
  { hour: "12 PM", minutes: 60 },
  { hour: "1 PM", minutes: 0 },
  { hour: "2 PM", minutes: 0 },
  { hour: "3 PM", minutes: 30 },
  { hour: "4 PM", minutes: 95 },
  { hour: "5 PM", minutes: 70 },
  { hour: "6 PM", minutes: 10 },
  { hour: "7 PM", minutes: 0 },
];

export default function Example() {
  return (
    <div className="w-full max-w-xl">
      <AreaChart
        curve="monotone"
        data={buildMinutes}
        label="Build minutes by hour"
        series={[{ key: "minutes", label: "Build minutes" }]}
        x="hour"
      />
    </div>
  );
}
```

## Negative values

An area fills between its line and 0, so values below 0 fill downward, and the value axis reaches below 0 to take them in. Stacked, the positive values stack up from 0 and the negative values down. The gradient is strongest at the edge away from 0 and fades toward the 0 line on both sides, so an area below 0 reads as the mirror of one above it.

Customers gained and lost, stacked around 0

[Complete TSX file](/examples/area-chart/negative.tsx)

```tsx
"use client";

import { AreaChart } from "@/siteplane/components/area-chart";

const customers = [
  { month: "Jan", churned: -38, new: 142, reactivated: 24 },
  { month: "Feb", churned: -45, new: 156, reactivated: 31 },
  { month: "Mar", churned: -52, new: 171, reactivated: 28 },
  { month: "Apr", churned: -41, new: 165, reactivated: 35 },
  { month: "May", churned: -57, new: 188, reactivated: 33 },
  { month: "Jun", churned: -49, new: 204, reactivated: 41 },
];

export default function Example() {
  return (
    <div className="w-full max-w-xl">
      <AreaChart
        data={customers}
        label="Customers gained and lost by month"
        series={[
          { key: "new", label: "New" },
          { key: "reactivated", label: "Reactivated" },
          { key: "churned", label: "Churned" },
        ]}
        stacked
        x="month"
      />
    </div>
  );
}
```

## Fill height

With `height="fill"`, the chart is as tall as its box, which takes its height from the container: a flex item that grows, a grid cell, or a box with a height of its own. The chart never goes lower than `minHeight`, 240 px by default, and keeps that height until it has measured its box, on the server too. Its own content never makes the box taller, so a card can hand the chart whatever room the rest of the card leaves. Here the grid row is as tall as the list beside the chart, and the chart fills the room its card leaves in that row. The [Bar Chart](/docs/components/bar-chart/#fill-height) fills its box the same way.

An area chart that fills its card beside a taller card

[Complete TSX file](/examples/area-chart/fill-height.tsx)

```tsx
"use client";

import { AreaChart } from "@/siteplane/components/area-chart";
import {
  Card,
  CardDescription,
  CardHeader,
  CardPanel,
  CardTitle,
} from "@/siteplane/components/card";

const visitors = [
  { month: "Jan", visitors: 18200 },
  { month: "Feb", visitors: 21400 },
  { month: "Mar", visitors: 19800 },
  { month: "Apr", visitors: 24600 },
  { month: "May", visitors: 27300 },
  { month: "Jun", visitors: 25900 },
];

const pages = [
  { path: "/", views: "96.4K" },
  { path: "/pricing", views: "42.1K" },
  { path: "/docs", views: "38.7K" },
  { path: "/docs/installation", views: "21.5K" },
  { path: "/blog", views: "17.9K" },
  { path: "/changelog", views: "12.3K" },
  { path: "/about", views: "6.8K" },
  { path: "/contact", views: "3.2K" },
];

export default function Example() {
  return (
    <div className="grid w-full max-w-xl gap-4 sm:grid-cols-[minmax(0,1fr)_12rem]">
      <Card>
        <CardHeader>
          <CardTitle>Visitors</CardTitle>
          <CardDescription>January to June</CardDescription>
        </CardHeader>
        <CardPanel className="flex flex-col">
          <AreaChart
            className="flex-1"
            data={visitors}
            height="fill"
            label="Visitors by month"
            minHeight={160}
            series={[{ key: "visitors", label: "Visitors" }]}
            x="month"
          />
        </CardPanel>
      </Card>
      <Card>
        <CardHeader>
          <CardTitle>Top pages</CardTitle>
          <CardDescription>Views, January to June</CardDescription>
        </CardHeader>
        <CardPanel>
          <ul className="grid gap-3 text-sm">
            {pages.map((page) => (
              <li className="flex justify-between gap-4" key={page.path}>
                <span className="truncate">{page.path}</span>
                <span className="text-muted-foreground tabular-nums">
                  {page.views}
                </span>
              </li>
            ))}
          </ul>
        </CardPanel>
      </Card>
    </div>
  );
}
```

## Changing data

New data glides in: the areas morph to it, whatever its length, and the value axis moves with them. Axis labels fade through, the old ones out before the new ones in. A series with a gap or a single point, and a change in the number of series, show at once.

Stacked visits that glide when the period changes

[Complete TSX file](/examples/area-chart/changing.tsx)

```tsx
"use client";

import { useState } from "react";
import { AreaChart } from "@/siteplane/components/area-chart";
import {
  ToggleGroup,
  ToggleGroupItem,
} from "@/siteplane/components/toggle-group";

const periods = [
  { days: "7", label: "7 days" },
  { days: "30", label: "30 days" },
  { days: "90", label: "90 days" },
];

const day = 24 * 60 * 60 * 1000;
const lastDay = Date.UTC(2026, 8, 30);

// The visits of a day follow from its date, so every period ends on the same days.
function visitsFor(days: number) {
  return Array.from({ length: days }, (_, index) => {
    const time = lastDay - (days - 1 - index) * day;
    const t = time / day;
    const direct = Math.round(
      1100 + 260 * Math.sin(t / 2.6) + 150 * Math.cos(t / 7.3),
    );
    return {
      date: new Date(time),
      direct,
      search: Math.round(direct * 0.8 + 130 * Math.sin(t / 3.9)),
    };
  });
}

export default function Example() {
  const [days, setDays] = useState("30");

  return (
    <div className="grid w-full max-w-xl gap-4">
      <ToggleGroup
        aria-label="Period"
        onValueChange={(next) => {
          if (next[0]) setDays(next[0]);
        }}
        size="sm"
        value={[days]}
        variant="outline"
      >
        {periods.map((period) => (
          <ToggleGroupItem key={period.days} value={period.days}>
            {period.label}
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
      <AreaChart
        data={visitsFor(Number(days))}
        label="Visits by source and day"
        series={[
          { key: "direct", label: "Direct" },
          { key: "search", label: "Search" },
        ]}
        stacked
        x="date"
      />
    </div>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| AreaChart.data | readonly ChartDatum[] | required | Rows of the chart: an x value and one number per series. |
| AreaChart.x | string | required | Key of the x value in each row: category text, or a Date for a time axis. |
| AreaChart.series | readonly ChartSeries[] | required | The series to draw, in order. |
| AreaChart.label | string | required | Accessible name of the chart. |
| AreaChart.aria-describedby | string | unset | Id of an element that describes the chart, read after the summary of the data that the chart gives screen readers. |
| AreaChart.curve | monotone \| smooth \| linear \| step | monotone | How the points are joined: monotone bends through every point and never swings past the data, smooth is a natural spline that can swing a little past the data between two points, linear draws straight segments and step holds each value until the middle between two points. |
| AreaChart.stacked | boolean | false | Stacks the areas on each other in the order of series: positive values up from 0, negative values down. |
| AreaChart.fill | gradient \| solid | gradient | gradient fades the series color out toward the base of the area, downward above 0 and upward below it; solid fills it with a flat tint. |
| AreaChart.height | number \| fill | 240 | Height of the chart in pixels, the legend included, or fill to take the height of its box down to minHeight; the width follows the container. |
| AreaChart.minHeight | number | 240 | With height fill, the least height of the chart in pixels, the legend included. |
| AreaChart.palette | brand \| categorical | brand | brand shades the brand color from strong to light; categorical gives each series its own chart color. A series’ own color wins either way. |
| AreaChart.legend | boolean | true with more than one series | Lists the series below the plot. |
| AreaChart.grid | boolean | true | Grid lines at the value axis ticks. |
| AreaChart.xAxis | boolean | true | Labels along the x axis. |
| AreaChart.yAxis | boolean | true | Labels along the value axis. |
| AreaChart.formatValue | (value: number) => string | locale number (1,234.5); compact on the axis (1.2K) | Formats values in the tooltip and on the value axis. |
| AreaChart.formatX | (value: ChartXValue) => string | text as given; a date as Jan 5, and Jan 5, 2026 in the tooltip | Formats x values on the axis and in the tooltip heading. |
| AreaChart.locale | string | en-US | Locale of numbers and dates. |
| AreaChart.emptyLabel | string | No data | Shown in place of the marks when there is nothing to draw. |
| AreaChart.labels | Partial<CartesianChartLabels> | English words | Replaces the words that the chart gives screen readers, each one on its own. Pass their translation when the application is in another language. |
| AreaChart.className | string | unset | Additional classes. |
| ChartSeries.key | string | required | Key of the numbers of this series in each row. |
| ChartSeries.label | string | required | Name shown in the legend and the tooltip. |
| ChartSeries.color | string | palette color | A CSS color, such as var(--chart-3), that replaces the palette color of this series. |
| CartesianChartLabels.roleDescription | string | chart | What screen readers call the chart. |
| CartesianChartLabels.keyboardHint | string | Use the arrow keys to move between data points. | Read after the description of the chart while it is a focus stop. An empty text leaves the hint out. |
| CartesianChartLabels.to | string | to | Joins the first and the last x value in the summary of the data, as in Jan to Jun. |
| CartesianChartLabels.lowest | string | lowest | Comes before the lowest value of each series in the summary. |
| CartesianChartLabels.highest | string | highest | Comes before the highest value of each series in the summary. |
AreaChartProps exports this contract. ChartDatum, ChartSeries, ChartXValue,
ChartCurve, ChartPalette and CartesianChartLabels come from the chart core,
`@/siteplane/components/chart`, which installs with the chart. The chart renders
a figure with the plot and, for several series, a legend; it takes no native
props or ref beyond `className` and `aria-describedby`. The categorical palette
has eight chart colors and the brand palette spreads up to eight shades, and
both repeat after the eighth series.

[Read the installed source](/source/area-chart).

## Usage notes

Use an area chart when the size of a measure over time matters, or, stacked, how its parts add up to a total. Areas that overlap can hide each other: stack them, or use a [Line Chart](/docs/components/line-chart/) for several series that are not parts of one whole. When the categories have no order, such as countries, use a [Bar Chart](/docs/components/bar-chart/). The axis gives a rough scale and the tooltip one point at a time, so show a [Table](/docs/components/table/) when readers need exact values. Several series share one value axis, which always includes 0; there is no second axis.

A missing value (`null`, `undefined` or anything that is not a number) breaks the area at that point and leaves the series out of the tooltip there; in a stack it adds nothing. A point on its own is drawn as a dot. Date values on the x axis are written in UTC, so use dates at midnight UTC for days. Text values are categories, shown in the order given.

The plot is one focus stop. On hover, focus or tap, the nearest x position becomes the active item, and the chart shows:

- a hairline crosshair;
- a dot per series that glides to it;
- an inverse label on the axis that names the item;
- the tooltip card beside the crosshair, trailing it. The card’s values roll to the new ones.

The rest of the chart steps back slightly: the areas go to 90%, and the stretch of their outline around the active point stays at full strength. The legend is on by default when there is more than one series, and sits below the plot. A mouse over an entry steps the other series back; the legend does not toggle series, and the chart has no click or selection API.

Left and Right arrow keys step through the x positions, Home and End jump to the ends, and Escape closes the tooltip. Keyboard focus starts at the first x position. A tap or pen press pins the item until the next press elsewhere or Escape. Screen readers meet the plot as a chart named by `label`. Its description sums up the data with the first and the last x value and the lowest and the highest value of each series; a chart with no value is described by `emptyLabel`. While the chart is a focus stop, a hint that the arrow keys move between the data points follows the description. `labels` replaces these words and the role description, for example with their translation. While the keyboard or a tap moves between the items, a polite live region reads what the tooltip shows. Each row of the tooltip names its series, so the color never carries the information alone. A chart with no values is not a focus stop.

Everything that moves runs on the [Motion](/docs/components/motion/) tokens: new data glides over `--motion-duration-value`, and the crosshair, the dots and the axis label follow the active item over `--motion-duration-follow`, with the card trailing over `--motion-duration-follow-trail`. All of it is instant under reduced motion: the system setting, `html` with `data-motion-reduce="true"` or `SiteplaneMotionProvider preset="reduced"`. The chart draws its plot once it has measured the width of its container. Until then, and in the server HTML, it renders the box of the plot at its full height, the legend and what screen readers read, so nothing on the page moves when the plot appears. The tooltip comes from [Tooltip](/docs/components/tooltip/). The chart opens and closes it itself, so it needs no `TooltipProvider`, which only shares hover delays between tooltips.
