Skip to content
Nizivo UI

AreaChart

A time series drawn on the server, this period in brand green with a 10% wash and an optional dashed comparison, with one interactive layer for the crosshair, the tooltip and keyboard access to every point.

Import

import { AreaChart } from '@nizivo/charts/area-chart';

Static or interactive

Split. AreaChart's SVG, tick labels, legend and data table are server HTML. Its module imports one client module, the ChartInteraction island, which it renders only when the status is 'ready' and interaction is 'nizivo' (the default). With interaction="none" it renders no island and the plot has no Tab stop. The data table is then the screen-reader path; pass tableView="details" so sighted keyboard users can reach the values too.

As with Field and IconButton in @nizivo/react, the shell imports its leaf: Turbopack and next dev ship a client module that a page's server code imports, rendered or not. To ship no chart JavaScript, draw the chart with a part that imports no client module. The Next.js fixture's page for this case (fixtures/next/app/chart-area-static, recorded in week 10) shows it: Turbopack and next dev ship the imported island (1,056 B gzip under Turbopack in week 10; the island has grown since, see Sizes), and webpack ships nothing (fixtures/next/RESULTS.md, "Week 10"). Each charts page has its own route, so on this site the island loads on this page and the BarChart page only. How to choose.

Long series. Measured on 2026-10-20 (M40) with a comparison series and USD values, a year of daily points (365) is about 82 KB of server HTML (15.5 KB gzip) and 164 KB of RSC payload (28.8 KB gzip), of which 23 KB is the island's props: about 64 B of JSON a point (week 10: about 335 B). The same year bucketed weekly (52 points) is 14 KB of HTML (3.1 KB gzip) and 26 KB of RSC payload (5 KB gzip). area-chart.server.test.tsx in packages/charts/test/ssr holds the island's props to 50 to 80 B a point at 365 points. Bytes no longer force bucketing at 90 points (13.9 KB gzip in all, the island's code included), but on Figma's 720 px plot a daily year leaves 2 px a point and 365 arrow-key steps, so above about 90 points, bucket daily data weekly before passing it in.

Examples

Basic

SalesDaily sales, Oct 1–30, vs previous period
  • This period
  • Previous
Arrow keys move between points; Home and End go to the first and last
DateThis periodPrevious
Thu, Oct 1$2,200$2,980
Fri, Oct 2$2,712$3,014
Sat, Oct 3$3,169$3,009
Sun, Oct 4$3,527$2,970
Mon, Oct 5$3,401$2,909
Tue, Oct 6$3,513$2,839
Wed, Oct 7$3,530$2,772
Thu, Oct 8$3,495$2,723
Fri, Oct 9$3,106$2,702
Sat, Oct 10$3,136$2,719
Sun, Oct 11$3,269$2,777
Mon, Oct 12$3,525$2,876
Tue, Oct 13$3,543$3,012
Wed, Oct 14$4,013$3,174
Thu, Oct 15$4,530$3,353
Fri, Oct 16$5,036$3,533
Sat, Oct 17$5,118$3,701
Sun, Oct 18$5,455$3,845
Mon, Oct 19$5,667$3,955
Tue, Oct 20$5,760$4,025
Wed, Oct 21$5,403$4,052
Thu, Oct 22$5,365$4,041
Fri, Oct 23$5,343$3,999
Sat, Oct 24$5,389$3,936
Sun, Oct 25$5,182$3,865
Mon, Oct 26$5,462$3,800
Tue, Oct 27$5,858$3,754
Wed, Oct 28$6,341$3,739
Thu, Oct 29$6,499$3,762
Fri, Oct 30$6,998$3,826
import type { JSX } from 'react';
import { AreaChart } from '@nizivo/charts/area-chart';

interface Day {
  date: string;
  current: number;
  previous: number;
}

// Thirty days of sales, as Figma 03 draws them (Oct 1–30, this period vs the previous one).
// Deterministic, so the visual lane's shots are stable.
const DAYS: Day[] = Array.from({ length: 30 }, (_, i) => ({
  date: `2026-10-${String(i + 1).padStart(2, '0')}`,
  current: Math.round(2200 + i * 150 + Math.sin(i / 2.2) * 620 + (i % 4) * 90),
  previous: Math.round(2600 + i * 55 + Math.cos(i / 3) * 380),
}));

const USD = { style: 'currency', currency: 'USD', maximumFractionDigits: 0 } as const;

export function AreaChartExample(): JSX.Element {
  return (
    <AreaChart
      title="Sales"
      summary="Daily sales, Oct 1–30, vs previous period"
      data={DAYS}
      x="date"
      y="current"
      compare="previous"
      format={USD}
    />
  );
}

Single

One series, no comparison: no legend, no delta.

Orders
Arrow keys move between points; Home and End go to the first and last
DateThis period
Thu, Oct 152
Fri, Oct 265
Sat, Oct 375
Sun, Oct 484
Mon, Oct 581
Tue, Oct 684
Wed, Oct 784
Thu, Oct 883
Fri, Oct 974
Sat, Oct 1075
Sun, Oct 1178
Mon, Oct 1284
Tue, Oct 1384
Wed, Oct 1496
import type { JSX } from 'react';
import { AreaChart } from '@nizivo/charts/area-chart';

interface Day {
  date: string;
  current: number;
  previous: number;
}

// Thirty days of sales, as Figma 03 draws them (Oct 1–30, this period vs the previous one).
// Deterministic, so the visual lane's shots are stable.
const DAYS: Day[] = Array.from({ length: 30 }, (_, i) => ({
  date: `2026-10-${String(i + 1).padStart(2, '0')}`,
  current: Math.round(2200 + i * 150 + Math.sin(i / 2.2) * 620 + (i % 4) * 90),
  previous: Math.round(2600 + i * 55 + Math.cos(i / 3) * 380),
}));

export function AreaChartSingleExample(): JSX.Element {
  return (
    <AreaChart
      title="Orders"
      data={DAYS.slice(0, 14)}
      x="date"
      y={(day) => Math.round(day.current / 42)}
    />
  );
}

Static

The data table as a native disclosure under the plot, and no keyboard layer (zero JS).

Sales
Show data table
DateThis period
Thu, Oct 1$2,200
Fri, Oct 2$2,712
Sat, Oct 3$3,169
Sun, Oct 4$3,527
Mon, Oct 5$3,401
Tue, Oct 6$3,513
Wed, Oct 7$3,530
Thu, Oct 8$3,495
Fri, Oct 9$3,106
Sat, Oct 10$3,136
Sun, Oct 11$3,269
Mon, Oct 12$3,525
Tue, Oct 13$3,543
Wed, Oct 14$4,013
Thu, Oct 15$4,530
Fri, Oct 16$5,036
Sat, Oct 17$5,118
Sun, Oct 18$5,455
Mon, Oct 19$5,667
Tue, Oct 20$5,760
Wed, Oct 21$5,403
Thu, Oct 22$5,365
Fri, Oct 23$5,343
Sat, Oct 24$5,389
Sun, Oct 25$5,182
Mon, Oct 26$5,462
Tue, Oct 27$5,858
Wed, Oct 28$6,341
Thu, Oct 29$6,499
Fri, Oct 30$6,998
import type { JSX } from 'react';
import { AreaChart } from '@nizivo/charts/area-chart';

interface Day {
  date: string;
  current: number;
  previous: number;
}

// Thirty days of sales, as Figma 03 draws them (Oct 1–30, this period vs the previous one).
// Deterministic, so the visual lane's shots are stable.
const DAYS: Day[] = Array.from({ length: 30 }, (_, i) => ({
  date: `2026-10-${String(i + 1).padStart(2, '0')}`,
  current: Math.round(2200 + i * 150 + Math.sin(i / 2.2) * 620 + (i % 4) * 90),
  previous: Math.round(2600 + i * 55 + Math.cos(i / 3) * 380),
}));

const USD = { style: 'currency', currency: 'USD', maximumFractionDigits: 0 } as const;

export function AreaChartStaticExample(): JSX.Element {
  return (
    <AreaChart
      title="Sales"
      data={DAYS}
      x="date"
      y="current"
      format={USD}
      interaction="none"
      tableView="details"
    />
  );
}

Status

Loading, empty and error keep the chart's box (§16.5).

Sales
Loading chart
Sales

No data to show

Sales

This chart could not be loaded

import type { JSX } from 'react';
import { AreaChart } from '@nizivo/charts/area-chart';

interface Day {
  date: string;
  current: number;
  previous: number;
}

// Thirty days of sales, as Figma 03 draws them (Oct 1–30, this period vs the previous one).
// Deterministic, so the visual lane's shots are stable.
const DAYS: Day[] = Array.from({ length: 30 }, (_, i) => ({
  date: `2026-10-${String(i + 1).padStart(2, '0')}`,
  current: Math.round(2200 + i * 150 + Math.sin(i / 2.2) * 620 + (i % 4) * 90),
  previous: Math.round(2600 + i * 55 + Math.cos(i / 3) * 380),
}));

export function AreaChartStatusExample(): JSX.Element {
  return (
    <div>
      <AreaChart title="Sales" status="loading" data={DAYS} x="date" y="current" />
      <AreaChart title="Sales" data={[] as Day[]} x="date" y="current" />
      <AreaChart title="Sales" status="error" data={DAYS} x="date" y="current" />
    </div>
  );
}

Right to left

Time mirrored for a right-to-left page that wants it (xDirection="rtl"; the default keeps ltr).

المبيعات
  • هذه الفترة
  • الفترة السابقة
Arrow keys move between points; Home and End go to the first and last
Dateهذه الفترةالفترة السابقة
الخميس، ١ أكتوبر٢٬٢٠٠٢٬٩٨٠
الجمعة، ٢ أكتوبر٢٬٧١٢٣٬٠١٤
السبت، ٣ أكتوبر٣٬١٦٩٣٬٠٠٩
الأحد، ٤ أكتوبر٣٬٥٢٧٢٬٩٧٠
الاثنين، ٥ أكتوبر٣٬٤٠١٢٬٩٠٩
الثلاثاء، ٦ أكتوبر٣٬٥١٣٢٬٨٣٩
الأربعاء، ٧ أكتوبر٣٬٥٣٠٢٬٧٧٢
الخميس، ٨ أكتوبر٣٬٤٩٥٢٬٧٢٣
الجمعة، ٩ أكتوبر٣٬١٠٦٢٬٧٠٢
السبت، ١٠ أكتوبر٣٬١٣٦٢٬٧١٩
الأحد، ١١ أكتوبر٣٬٢٦٩٢٬٧٧٧
الاثنين، ١٢ أكتوبر٣٬٥٢٥٢٬٨٧٦
الثلاثاء، ١٣ أكتوبر٣٬٥٤٣٣٬٠١٢
الأربعاء، ١٤ أكتوبر٤٬٠١٣٣٬١٧٤
الخميس، ١٥ أكتوبر٤٬٥٣٠٣٬٣٥٣
الجمعة، ١٦ أكتوبر٥٬٠٣٦٣٬٥٣٣
السبت، ١٧ أكتوبر٥٬١١٨٣٬٧٠١
الأحد، ١٨ أكتوبر٥٬٤٥٥٣٬٨٤٥
الاثنين، ١٩ أكتوبر٥٬٦٦٧٣٬٩٥٥
الثلاثاء، ٢٠ أكتوبر٥٬٧٦٠٤٬٠٢٥
الأربعاء، ٢١ أكتوبر٥٬٤٠٣٤٬٠٥٢
الخميس، ٢٢ أكتوبر٥٬٣٦٥٤٬٠٤١
الجمعة، ٢٣ أكتوبر٥٬٣٤٣٣٬٩٩٩
السبت، ٢٤ أكتوبر٥٬٣٨٩٣٬٩٣٦
الأحد، ٢٥ أكتوبر٥٬١٨٢٣٬٨٦٥
الاثنين، ٢٦ أكتوبر٥٬٤٦٢٣٬٨٠٠
الثلاثاء، ٢٧ أكتوبر٥٬٨٥٨٣٬٧٥٤
الأربعاء، ٢٨ أكتوبر٦٬٣٤١٣٬٧٣٩
الخميس، ٢٩ أكتوبر٦٬٤٩٩٣٬٧٦٢
الجمعة، ٣٠ أكتوبر٦٬٩٩٨٣٬٨٢٦
import type { JSX } from 'react';
import { AreaChart } from '@nizivo/charts/area-chart';

interface Day {
  date: string;
  current: number;
  previous: number;
}

// Thirty days of sales, as Figma 03 draws them (Oct 1–30, this period vs the previous one).
// Deterministic, so the visual lane's shots are stable.
const DAYS: Day[] = Array.from({ length: 30 }, (_, i) => ({
  date: `2026-10-${String(i + 1).padStart(2, '0')}`,
  current: Math.round(2200 + i * 150 + Math.sin(i / 2.2) * 620 + (i % 4) * 90),
  previous: Math.round(2600 + i * 55 + Math.cos(i / 3) * 380),
}));

export function AreaChartRtlExample(): JSX.Element {
  return (
    <div dir="rtl">
      <AreaChart
        title="المبيعات"
        data={DAYS}
        x="date"
        y="current"
        compare="previous"
        locale="ar-EG"
        format={{ maximumFractionDigits: 0 }}
        axisFormat={{ maximumFractionDigits: 0 }}
        xDirection="rtl"
        labels={{ current: 'هذه الفترة', previous: 'الفترة السابقة', delta: 'مقارنة بالسابقة' }}
      />
    </div>
  );
}

Props

Server component. Props: AreaChartProps<T>. Source: src/area-chart/area-chart.tsx.

NameTypeDefaultDescription
axesChartAxes
"frame""none"
'frame'

From ChartFrameProps.

axisFormatIntl.NumberFormatOptions`format` in compact notation with at most one fraction digit ("$8K")

The y labels.

From CartesianChartOptions.

compareAccessor<T, number | null | undefined>–

The comparison series, drawn dashed in chart/neutral (decorative: the table carries it).

curveCurveFactorycurveMonotoneX (no overshoot below the baseline)

Any d3-shape curve factory.

datarequiredreadonly T[]–

In x order: one datum per point or bar.

From CartesianChartOptions.

dateFormatIntl.DateTimeFormatOptions{ month: 'short', day: 'numeric' } ("Oct 1")

ISO plain dates on the x axis.

From CartesianChartOptions.

emptyReactNode–

Replaces the default empty message.

From ChartFrameProps.

errorReactNode–

Replaces the default error message, for example an InlineError with a retry action.

From ChartFrameProps.

formatIntl.NumberFormatOptionsIntl.NumberFormat's own ({})

Values in the tooltip and the data table.

From CartesianChartOptions.

interactionChartInteractionMode
"nizivo""none"
'nizivo'

From ChartFrameProps.

labelsPartial<AreaChartLabels>–
legendReactNode–

true shows the series key; the default shows it when there is a comparison.

localestring'en-US'

The BCP 47 locale every number and date is formatted in, once, on the server (§16.4).

From CartesianChartOptions.

longDateFormatIntl.DateTimeFormatOptions{ weekday: 'short', month: 'short', day: 'numeric' }

ISO plain dates in the tooltip and the data table.

From CartesianChartOptions.

pendingboolean–

A refresh is in flight while the chart shows data (§16.5): aria-busy and data-pending.

From ChartFrameProps.

renderRenderProp<ChartFrameState>–

From ChartFrameAttributes.

statusDataStatus
"ready""loading""empty""error"
'ready'; a ready chart with no table rows renders as `'empty'`.

From ChartFrameProps.

summaryReactNode–

One line under the title, for example "Daily sales, Oct 1–30, vs previous period".

From ChartFrameProps.

tableViewChartTableView
"hidden""details"
'hidden'

From ChartFrameProps.

titlerequiredReactNode–

The figure's caption and its accessible name.

From ChartFrameProps.

titleVisibilityChartTitleVisibility
"visible""hidden"
'visible'

'hidden' hides the title and summary visually; they still name the figure.

From ChartFrameProps.

xrequiredAccessor<T, string>–

The x value: an ISO plain date ('2026-10-14', formatted per locale) or any label.

From CartesianChartOptions.

xDirectionChartXDirection
"ltr""rtl""auto"
'ltr'

From ChartFrameProps.

xTicksChartXTicks–

From CartesianChartOptions.

yrequiredAccessor<T, number | null | undefined>–

The value. A value that is not a finite number is a gap (no point, no bar).

From CartesianChartOptions.

yTicksnumber4

About how many y labels; the value domain is niced for this count.

From CartesianChartOptions.

  • Also takes every <figure> attribute except children, title.

render takes an element, or, in client code, a function of the props and this state (ChartFrameState):

NameTypeDefaultDescription
interactionrequiredChartInteractionMode
"nizivo""none"
–

As passed: whether the child claims keyboard access to its points.

pendingrequiredboolean–
statusrequiredDataStatus
"ready""loading""empty""error"
–

The status as rendered: 'empty' when a ready chart has no rows.

x, y and compare are an Accessor: a key of the datum whose values have the right type (x="date", which a Server Component can pass), or a (datum, index) => value function. format and axisFormat are plain Intl.NumberFormatOptions, as @nizivo/react's Delta and Meter take them; there are no presets.

xTicks (ChartXTicks): at most max x labels (AreaChart: 6), the last point always labelled. When every is not given, a daily date series rounds the step up to whole weeks (Figma 03's Oct 1, 8, 15, 22 and the forced 30).

NameTypeDefaultDescription
everynumber–

Data points (or bars) between labels; the last one always has a label. Derived from max.

maxnumber–

At most this many labels, the first and last included. AreaChart: 6, BarChart: 8.

The props marked as from CartesianChartOptions are the ones AreaChart and BarChart share, with the same names and types (one API shape across the charts, M40). The defaults are the same too, except xTicks.max: 6 here, 8 for BarChart.

labels takes Partial<AreaChartLabels>, which extends ChartFrameLabels (its members are under Other exports). The English defaults, in areaChartLabels, on top of ChartFrame's:

KeyDefault
xDate
categoryCategory
currentThis period
previousPrevious
deltavs previous
missingNo value
interactioninteractive chart
hintArrow keys move between points; Home and End go to the first and last
pointsChart points

Other exports

AreaChartLabels type

import type { AreaChartLabels } from '@nizivo/charts/area-chart'

AreaChart's strings (§16.4), with ChartFrame's. English defaults in areaChartLabels.

NameTypeDefaultDescription
categoryrequiredstring–

The data table's first column header when x holds labels (BarChart's category).

currentrequiredstring–

The series' name in the legend, the tooltip and the table.

deltarequiredstring–

After the tooltip's change, for example "+28.0% vs previous".

hintrequiredstring–

The interactive layer's keyboard instructions.

interactionrequiredstring–

The interactive layer's role, as assistive technology names it.

missingrequiredstring–

A value the series does not have at that point (BarChart's missing too).

pointsrequiredstring–

The interactive layer's name when title is not a string.

previousrequiredstring–

The comparison's name.

xrequiredstring–

The data table's first column header when every x is an ISO plain date.

AreaChartProps type

import type { AreaChartProps } from '@nizivo/charts/area-chart'

AreaChart's props: the shared cartesian options (_core/api.ts), its own, and ChartFrame's, the <figure>'s attributes included.

The props of AreaChart, listed above.

areaChartLabels constant

import { areaChartLabels } from '@nizivo/charts/area-chart'

const areaChartLabels: AreaChartLabels

Data attributes and slots

Inside the ChartFrame root, which carries nz-area-chart:

ElementAttributes
svg.nz-chart-svgviewBox="0 0 100 100", preserveAspectRatio="none", aria-hidden; coordinates rounded to 2 decimals
line.nz-chart-grid, line.nz-chart-baselineOne grid line per tick other than 0; the baseline at 0
path.nz-chart-area, path.nz-chart-linedata-series="current", or "previous" for the dashed comparison
path.nz-area-chart-halo, .nz-area-chart-dotThe last value's dot
div.nz-chart-interactionThe island's layer: role="application", tabindex="0", aria-label, aria-roledescription, aria-describedby

CSS hooks

HookDefaultRead as
--nz-area-chart-halovar(--nz-surface, var(--nz-bg-surface))
  • stroke on .nz-area-chart-halo

Layers: nz.components. Classes: .nz-area-chart-dot, .nz-area-chart-halo. Runtime variables: --nz-surface.

Tokens this stylesheet reads (2)

--nz-bg-surface, --nz-chart-primary

--nz-area-chart-halo is the ring around the end dot: the surface behind the chart. When the hook is unset, the ring takes the shared --nz-surface that @nizivo/react's Card declares for its children (KpiTile and TrendCard are Cards, so a tinted tile's halo is its own surface), else --nz-bg-surface. Charts read --nz-surface only if it is there: without @nizivo/react on the page it is unset and the halo is --nz-bg-surface, and @nizivo/charts does not depend on @nizivo/react (M42). The aspect ratio is ChartFrame's --nz-chart-frame-aspect (18 / 7).

Accessibility

  • The picture is aria-hidden; the data table (one row per point, every series in a column) is the accessible alternative. The comparison line is decorative and its values are in the table.
  • With the island, the plot is one Tab stop: a role="application" layer named by the title, with the labels.interaction role description and the labels.hint description, both rendered on the server. application is the role that lets a screen reader pass the arrow keys through; it stays until the manual screen-reader pass (M40).
  • One polite live region (aria-live="polite", aria-atomic="true"), empty in the server HTML, speaks the point a key move reaches: the date, then each series' name and value, joined with the separators Intl.ListFormat writes in locale on the server. Hiding the card (Escape, the pointer leaving, leaving the layer) empties it, so the next key that shows the card speaks the active point again. A key that does not move the point (End at the last point) says nothing, as a slider at its end does. A pointer shows the point and says nothing (Figma 03).
  • The tooltip card is aria-hidden: it repeats what the live region and the table say.
  • Against WCAG 1.4.13 (Content on Hover or Focus), the card is dismissable: Escape hides it wherever focus is, a card the pointer opened included (Escape on the focused plot hides only the card, so an enclosing dialog stays open). It is persistent: no timeout. It never hides while the pointer is over the plot or the card, but it takes no pointer events, so it follows the point under the pointer and cannot be held on one point: not "hoverable" in 1.4.13's sense. chart-interaction.spec.tsx checks this in Chromium, WebKit and Firefox.
  • Showing a point moves nothing in the layout, and moving it adds no layout shift: the crosshair and the dots move by translate (chart-interaction.spec.tsx sweeps a pointer across the plot).
  • Right to left: see Right to left for what a page sees before and after the island hydrates.

Keyboard

KeysAction
TabFocuses the layer. A keyboard focus shows and speaks the active point, the last point at first.
ArrowRight / ArrowLeftThe next or previous point on screen; on a mirrored plot (xDirection="rtl", or "auto" on a right-to-left page) ArrowRight goes back in time.
Home / EndThe first or last point.
EscapeHides the card, wherever focus is; the point stays active.

Source: chart-interaction.client.tsx in packages/charts/src/_island, checked in Chromium, WebKit and Firefox by chart-interaction.spec.tsx in packages/charts/test/browser. Touch: a tap shows the point and keeps the card after the finger lifts (synthetic pointer events only; real touch hardware is not tested).

Figma

Area Chart, 7275:13336: State=Default 7275:13337 and State=Hover 7275:13358, 720 × 280. "Time series, 1 axis. Hover: crosshair + ringed dots + Chart Tooltip (Series=2)." The hover variant places the duplicate tooltip set 747:287; code treats 7275:13303 as the source. Figma's reveal path-trims the line and fades the area in after 120 ms; code reveals both by clip-path, because pathLength with non-scaling strokes is unverified. Notes: docs/figma/03-data-viz.md §4.4.

On this page