Area Chart
- Stable
- WCAG 2.2 evidence
- RTL
A stacked area chart: layers piled up over time with the title, range switch, actions and legend of
the card. Data driven: categories and series in, SVG out, rendered on the server from the same props. Accessibility: as every chart of the library the figure is named by title, described by summary and backed by a data table
with a total column (WCAG 1.1.1, 1.3.1); the plot is ONE tab stop with Left and Right, Home, End, Page Up and Page Down between
categories and a tooltip on hover and focus that Escape closes (2.1.1, 1.4.13). Layers differ by colour, fill pattern and legend
order (1.4.1); the draw-in is behind motion-safe:; the plot is laid out for its container width (1.4.10) and stays left-to-right
in right-to-left pages. Consumer duties: a title that asks the question, a summary with the main message, units, meaningful series names.
import { AreaChart } from "@nexera-ui/react";- 12
- examples
- 26
- props
- 6
- live controls
- 1
- platform
- 9
- WCAG criteria
- 1
- block uses it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { AreaChart } from "@nexera-ui/react";
<AreaChart />Examples 11
The same examples as Storybook, rendered live. Open Code to copy one.
Without patterns
Without patterns the layers differ by colour and order only: use it when the layers are labelled in the plot.
import { AreaChart, IconButton } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart actions={menu} />;
Overlapping
isStacked={false}: every series is drawn from the baseline as a translucent area with a line on top.
import { AreaChart, IconButton } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart actions={menu} />;
Smooth
import { AreaChart, IconButton } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart actions={menu} smooth />;
Range switch
A range switch that swaps the data, next to the menu in the header.
import { useState } from "react";
import { AreaChart, IconButton, type AreaChartProps } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const MONTHS = ["Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct"];
const ANNUAL = [42, 38, 30, 28, 34, 40, 52, 58, 64, 50, 44, 46];
const SICK = [14, 18, 16, 15, 12, 10, 9, 8, 10, 13, 15, 14];
const WFH = [30, 26, 28, 32, 34, 36, 38, 36, 34, 32, 30, 28];
const UNPAID = [6, 5, 4, 6, 5, 7, 8, 6, 5, 6, 7, 5];
const leave = {
title: "Leave taken by type",
description: "Days per month, all employees",
summary:
"Annual leave is the largest share every month and peaks in July at 64 days; sick leave is highest in winter.",
categories: MONTHS,
series: [
{ name: "Annual", data: ANNUAL },
{ name: "Sick", data: SICK },
{ name: "WFH", data: WFH, styleIndex: 4 },
{ name: "Unpaid", data: UNPAID, styleIndex: 2 },
],
} satisfies AreaChartProps;
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
export function RangeSwitch() {
const [selected, setSelected] = useState("12m");
const count = selected === "3m" ? 3 : selected === "6m" ? 6 : 12;
return (
<AreaChart
actions={menu}
categories={MONTHS.slice(-count)}
series={leave.series.map((s) => ({ ...s, data: s.data.slice(-count) }))}
range={{
"aria-label": "Range",
options: [
{ id: "3m", label: "3M" },
{ id: "6m", label: "6M" },
{ id: "12m", label: "12M" },
],
value: selected,
onChange: setSelected,
}}
/>
);
}
Loading
import { AreaChart, IconButton } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart actions={menu} state={{ status: "loading" }} />;
Empty
import { AreaChart, IconButton, type AreaChartProps } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const MONTHS = ["Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct"];
const ANNUAL = [42, 38, 30, 28, 34, 40, 52, 58, 64, 50, 44, 46];
const SICK = [14, 18, 16, 15, 12, 10, 9, 8, 10, 13, 15, 14];
const WFH = [30, 26, 28, 32, 34, 36, 38, 36, 34, 32, 30, 28];
const UNPAID = [6, 5, 4, 6, 5, 7, 8, 6, 5, 6, 7, 5];
const leave = {
title: "Leave taken by type",
description: "Days per month, all employees",
summary:
"Annual leave is the largest share every month and peaks in July at 64 days; sick leave is highest in winter.",
categories: MONTHS,
series: [
{ name: "Annual", data: ANNUAL },
{ name: "Sick", data: SICK },
{ name: "WFH", data: WFH, styleIndex: 4 },
{ name: "Unpaid", data: UNPAID, styleIndex: 2 },
],
} satisfies AreaChartProps;
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart
actions={menu}
categories={[]}
series={leave.series.map((s) => ({ ...s, data: [] }))}
state={{ status: "empty", title: "No leave taken", description: "Pick another period." }}
/>;
Error state
import { AreaChart, Button, IconButton, type AreaChartProps } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const MONTHS = ["Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct"];
const ANNUAL = [42, 38, 30, 28, 34, 40, 52, 58, 64, 50, 44, 46];
const SICK = [14, 18, 16, 15, 12, 10, 9, 8, 10, 13, 15, 14];
const WFH = [30, 26, 28, 32, 34, 36, 38, 36, 34, 32, 30, 28];
const UNPAID = [6, 5, 4, 6, 5, 7, 8, 6, 5, 6, 7, 5];
const leave = {
title: "Leave taken by type",
description: "Days per month, all employees",
summary:
"Annual leave is the largest share every month and peaks in July at 64 days; sick leave is highest in winter.",
categories: MONTHS,
series: [
{ name: "Annual", data: ANNUAL },
{ name: "Sick", data: SICK },
{ name: "WFH", data: WFH, styleIndex: 4 },
{ name: "Unpaid", data: UNPAID, styleIndex: 2 },
],
} satisfies AreaChartProps;
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart
actions={menu}
state={{
status: "error",
title: "Couldn't load leave",
description: "Check your connection and try again.",
action: (
<Button variant="secondary" size="sm">
Try again
</Button>
),
}}
/>;
Single point
One month: a single stack, with its total in the tooltip and the table.
import { AreaChart, IconButton, type AreaChartProps } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const MONTHS = ["Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct"];
const ANNUAL = [42, 38, 30, 28, 34, 40, 52, 58, 64, 50, 44, 46];
const SICK = [14, 18, 16, 15, 12, 10, 9, 8, 10, 13, 15, 14];
const WFH = [30, 26, 28, 32, 34, 36, 38, 36, 34, 32, 30, 28];
const UNPAID = [6, 5, 4, 6, 5, 7, 8, 6, 5, 6, 7, 5];
const leave = {
title: "Leave taken by type",
description: "Days per month, all employees",
summary:
"Annual leave is the largest share every month and peaks in July at 64 days; sick leave is highest in winter.",
categories: MONTHS,
series: [
{ name: "Annual", data: ANNUAL },
{ name: "Sick", data: SICK },
{ name: "WFH", data: WFH, styleIndex: 4 },
{ name: "Unpaid", data: UNPAID, styleIndex: 2 },
],
} satisfies AreaChartProps;
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart
actions={menu}
title="Leave taken in October"
description="One month"
summary="In October staff took 93 days of leave."
categories={["Oct"]}
series={leave.series.map((s) => ({ ...s, data: s.data.slice(-1) }))}
/>;
Many points
300 points: labels thin out to what fits and the keys still reach every day.
import { AreaChart, IconButton } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
const DAYS = Array.from({ length: 300 }, (_, i) => `Day ${String(i + 1)}`);
<AreaChart
actions={menu}
title="Support tickets by priority"
description="Per day"
summary="Tickets followed a weekly rhythm."
categories={DAYS}
series={[
{ name: "Normal", data: DAYS.map((_, i) => 30 + (i % 7) * 4) },
{ name: "High", data: DAYS.map((_, i) => 10 + (i % 5) * 2) },
{ name: "Urgent", data: DAYS.map((_, i) => 2 + (i % 3)) },
]}
/>;
Long labels
Long titles and series names wrap in the header, the legend and the tooltip; x labels thin out.
import { AreaChart, IconButton, type AreaChartProps } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const MONTHS = ["Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct"];
const ANNUAL = [42, 38, 30, 28, 34, 40, 52, 58, 64, 50, 44, 46];
const SICK = [14, 18, 16, 15, 12, 10, 9, 8, 10, 13, 15, 14];
const WFH = [30, 26, 28, 32, 34, 36, 38, 36, 34, 32, 30, 28];
const UNPAID = [6, 5, 4, 6, 5, 7, 8, 6, 5, 6, 7, 5];
const leave = {
title: "Leave taken by type",
description: "Days per month, all employees",
summary:
"Annual leave is the largest share every month and peaks in July at 64 days; sick leave is highest in winter.",
categories: MONTHS,
series: [
{ name: "Annual", data: ANNUAL },
{ name: "Sick", data: SICK },
{ name: "WFH", data: WFH, styleIndex: 4 },
{ name: "Unpaid", data: UNPAID, styleIndex: 2 },
],
} satisfies AreaChartProps;
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart
actions={menu}
title="Leave taken by type across all departments, locations and employment contracts"
description="Days per calendar quarter, all employees including contractors on fixed-term agreements"
summary="Annual leave dominates."
categories={[
"Quarter four of the 2025 financial year",
"Quarter one of the 2026 financial year",
"Quarter two of the 2026 financial year",
]}
series={[
{ name: "Annual leave including carried-over days", data: [120, 90, 140] },
{ name: "Sick leave with and without a medical certificate", data: [40, 55, 30] },
]}
/>;
Right to left
Right-to-left page: card, legend and table mirror, the plot keeps its left-to-right time axis, digits follow the locale.
import { AreaChart, IconButton } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const ANNUAL = [42, 38, 30, 28, 34, 40, 52, 58, 64, 50, 44, 46];
const SICK = [14, 18, 16, 15, 12, 10, 9, 8, 10, 13, 15, 14];
const WFH = [30, 26, 28, 32, 34, 36, 38, 36, 34, 32, 30, 28];
const UNPAID = [6, 5, 4, 6, 5, 7, 8, 6, 5, 6, 7, 5];
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<AreaChart
actions={menu}
title="الإجازات حسب النوع"
description="أيام في الشهر، لجميع الموظفين"
summary="الإجازة السنوية هي الأكبر في كل شهر."
categories={[
"نوف",
"ديس",
"يناير",
"فبراير",
"مارس",
"أبريل",
"مايو",
"يونيو",
"يوليو",
"أغسطس",
"سبتمبر",
"أكتوبر",
]}
series={[
{ name: "سنوية", data: ANNUAL },
{ name: "مرضية", data: SICK },
{ name: "عمل عن بُعد", data: WFH, styleIndex: 4 },
{ name: "بدون أجر", data: UNPAID, styleIndex: 2 },
]}
table={{ categoryLabel: "الشهر", label: "عرض البيانات كجدول" }}
totalLabel="المجموع"
/>;
Props 26
Press "Try it" on a card to load that prop into the playground.
26 props shown
categories*Nexerareadonly string[]The x labels, one per data point ("Nov", "Dec",...). The first value of every series belongs to the first category.
categoryTitlesNexerareadonly string[]The long form of each category for the tooltip, the point labels and the table.
series*Nexerareadonly ChartSeries[]The layers, bottom layer first. Each gets its own colour AND fill pattern (WCAG 1.4.1);
the legend toggles them. A gap (null) in a stack takes no height.
isStackedNexerabooleanPile the series on top of each other so the top edge is the total. Set false to draw every series
from the baseline as overlapping translucent areas with a line on top.
smoothNexerabooleanSmooth monotone curves instead of straight segments.
patternsNexerabooleanDraw a fill pattern (hatch, dots) over each layer except the first, in addition to its colour, so layers can be told apart without colour (WCAG 1.4.1). Turn off only when the layers are also labelled in the plot.
totalLabelNexerastringName of the stack total in the tooltip and the table. Translate it.
title*NexerastringChart title: the question the chart answers ("Headcount"). Required: it names the figure, the plot and the data table (WCAG 1.1.1).
descriptionNexeraReactNodeSecondary line under the title.
summary*NexerastringOne or two sentences with what the chart shows and its main message ("Headcount grew from 96 to 128 over twelve months"). Required: it describes the figure for assistive technology (WCAG 1.1.1); the data table has the details.
rangeNexeraChartRangePropsRange switch in the header: 3M, 6M, 12M. Pass the data of the chosen range back in.
actionsNexeraReactNodeExtra controls in the header, after the range switch.
kpiNexeraChartKpiPropsKPI row above the plot: the headline number with an optional label or change badge.
stateNexeraChartPlotStateThe state that replaces the plot: loading (skeleton), empty or error. A chart without a value to plot shows the empty state
by itself; pass { status: "empty", title, description, action } to give it your wording.
tableNexeraChartTableOptionsThe data table that is the text alternative: caption, first-column header and whether it is visible or only for assistive technology.
localeNexerastringLocale of digits and separators.
valueFormatNexeraNumberFormatOptionsIntl.NumberFormat options for values ({ style: "currency", currency: "PKR" }, { style: "percent" }). Serialisable, so it
works from a Server Component; formatValue needs a Client Component.
formatValueNexera(value: number) => stringFormats a value for tooltips, point labels and the table (full precision). Wins over valueFormat.
formatAxisValueNexera(value: number) => stringFormats an axis tick (short: "29M").
heightNexeranumberHeight of the plot in px. The card grows with the header, KPI and legend around it.
isLegendHiddenNexerabooleanHide the legend. The series stay named in the tooltip, the point labels and the table.
plotLabelNexerastringAccessible name of the plot group. Translate it.
noDataLabelNexerastringText for a missing value in the tooltip and the table. Translate it.
hiddenSeriesNexerareadonly string[]Names of the hidden series (controlled). Pair it with onHiddenSeriesChange.
defaultHiddenSeriesNexerareadonly string[]Names of the series hidden at first (uncontrolled).
onHiddenSeriesChangeNexera(hidden: readonly string[]) => voidCalled with the names of the hidden series when the user toggles a legend entry.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
7 direct · 2 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.1Use of ColorLevel A · supporting test
- 1.4.10ReflowLevel AA · tested directly
- 1.4.13Content on Hover or FocusLevel AA · tested directly
- 2.1.1KeyboardLevel A · tested directly
- 2.3.3Animation from InteractionsLevel AAA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
- 4.1.3Status MessagesLevel AA · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
<AreaChart className="shadow-sm" />Used in blocks
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.
- BadgeOverflowThe "+N" pill that stands in for the badges a row has no room for.