Line Chart
- Stable
- WCAG 2.2 evidence
- RTL
A line chart: a
card with the title, range switch, KPI, legend and a plot that is laid out for the width of its container. Data driven:
categories and series in, SVG out; the same props render the static SVG on the server. Accessibility: the figure is named by title and described by summary; every plotted value is also in a real data table
(WCAG 1.1.1, 1.3.1). The plot is ONE tab stop: Left and Right move between categories, Home and End jump to the ends, Page Up and
Page Down skip a tenth; a category is announced with all its values. Hovering or focusing a category shows a tooltip that stays
while hovered and closes with Escape (1.4.13, 2.1.1). Series differ by colour, dash and marker shape, and the legend
toggles them (1.4.1). The draw-in is behind motion-safe:. Labels thin out and the legend wraps at 320 px (1.4.10); the plot stays
left-to-right in right-to-left pages while the card mirrors, and digits follow the locale. Consumer duties: a title that asks the question, a summary with the main message, units in valueFormat, meaningful series names.
import { LineChart } from "@nexera-ui/react";- 12
- examples
- 30
- props
- 7
- live controls
- 1
- platform
- 9
- WCAG criteria
- 0
- blocks use 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 { LineChart } from "@nexera-ui/react";
<LineChart />Examples 11
The same examples as Storybook, rendered live. Open Code to copy one.
Comparison
Figma Chart/Line/Comparison: two series (actual and benchmark), a labelled danger target line, markers on every point and a callout. Series differ by colour, dash and marker shape, and the legend entries toggle them.
import { LineChart } from "@nexera-ui/react";
<LineChart />;
Smooth
Straight segments are the default; smooth draws a monotone curve that never overshoots the data.
import { LineChart } from "@nexera-ui/react";
<LineChart smooth />;
Range switch
Controls in the header: a range switch that swaps the data, and a menu button in the actions slot.
import { useState } from "react";
import { IconButton, LineChart } 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 MONTH_TITLES = [
"Nov 2025",
"Dec 2025",
"Jan 2026",
"Feb 2026",
"Mar 2026",
"Apr 2026",
"May 2026",
"Jun 2026",
"Jul 2026",
"Aug 2026",
"Sep 2026",
"Oct 2026",
];
const HEADCOUNT = [96, 99, 103, 101, 106, 110, 108, 114, 117, 121, 125, 128];
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
export function RangeSwitch() {
const [selected, setSelected] = useState("6m");
const count = selected === "3m" ? 3 : selected === "6m" ? 6 : 12;
return (
<LineChart
categories={MONTHS.slice(-count)}
categoryTitles={MONTH_TITLES.slice(-count)}
series={[{ name: "Headcount", data: HEADCOUNT.slice(-count) }]}
range={{ ...range, value: selected, onChange: setSelected }}
actions={menu}
/>
);
}
Loading
Loading, empty and error are ChartStates inside the card; the header stays.
import { LineChart } from "@nexera-ui/react";
<LineChart state={{ status: "loading" }} />;
Empty
import { LineChart } from "@nexera-ui/react";
const range = {
"aria-label": "Range",
options: [
{ id: "3m", label: "3M" },
{ id: "6m", label: "6M" },
{ id: "12m", label: "12M" },
],
} as const;
<LineChart
categories={[]}
series={[{ name: "Headcount", data: [] }]}
state={{
status: "empty",
title: "No headcount in this period",
description: "Try a longer range.",
}}
/>;
Error state
import { Button, LineChart } from "@nexera-ui/react";
<LineChart
state={{
status: "error",
title: "Couldn't load headcount",
description: "Check your connection and try again.",
action: (
<Button variant="secondary" size="sm">
Try again
</Button>
),
}}
/>;
Single point
One data point: the line cannot be drawn, so the point gets a marker.
import { LineChart } from "@nexera-ui/react";
<LineChart
title="Headcount today"
description="One reading"
summary="Headcount is 128 today."
categories={["Oct"]}
categoryTitles={["Oct 2026"]}
series={[{ name: "Headcount", data: [128] }]}
/>;
Missing values
Gaps in the data break the line and read "No data" in the tooltip and the table.
import { LineChart } from "@nexera-ui/react";
<LineChart
series={[
{ name: "Headcount", data: [96, 99, null, null, 106, 110, 108, null, 117, 121, 125, 128] },
]}
/>;
Many points
365 daily points: labels thin out to what fits, markers are dropped, and the keys still reach every day (Page Down skips 36).
import { LineChart } from "@nexera-ui/react";
const DAYS = Array.from({ length: 365 }, (_, i) => `Day ${String(i + 1)}`);
<LineChart
title="Daily active users"
description="Last 365 days"
summary="Daily active users followed a yearly cycle between 50 and 150."
categories={DAYS}
series={[
{ name: "Active users", data: DAYS.map((_, i) => Math.round(100 + 50 * Math.sin(i / 58))) },
]}
markers="all"
/>;
Long labels
Long category and series names: x labels thin out, legend entries and tooltip rows wrap, nothing is cut off.
import { LineChart } from "@nexera-ui/react";
<LineChart
title="Time to hire by hiring stage across all departments and locations"
description="Median days from application to signed offer, per calendar quarter"
summary="Time to hire fell in every stage."
categories={[
"Quarter four of the 2025 financial year",
"Quarter one of the 2026 financial year",
"Quarter two of the 2026 financial year",
"Quarter three of the 2026 financial year",
]}
series={[
{ name: "Application review and first recruiter screening call", data: [12, 10, 9, 8] },
{ name: "Hiring manager interview and technical assessment round", data: [21, 19, 18, 15] },
]}
/>;
Right to left
Right-to-left page: the card, legend and table mirror, the plot keeps its left-to-right time axis, digits follow the locale.
import { LineChart } from "@nexera-ui/react";
const HEADCOUNT = [96, 99, 103, 101, 106, 110, 108, 114, 117, 121, 125, 128];
<LineChart
title="عدد الموظفين"
description="آخر ١٢ شهرًا"
summary="ارتفع عدد الموظفين من ٩٦ إلى ١٢٨ خلال اثني عشر شهرًا."
categories={[
"نوف",
"ديس",
"يناير",
"فبراير",
"مارس",
"أبريل",
"مايو",
"يونيو",
"يوليو",
"أغسطس",
"سبتمبر",
"أكتوبر",
]}
series={[{ name: "عدد الموظفين", data: HEADCOUNT }]}
kpi={{ value: "128", label: "موظف" }}
range={{
"aria-label": "النطاق",
options: [
{ id: "3m", label: "٣ أشهر" },
{ id: "12m", label: "١٢ شهرًا" },
],
defaultValue: "12m",
}}
table={{ categoryLabel: "الشهر", label: "عرض البيانات كجدول" }}
/>;
Props 30
Press "Try it" on a card to load that prop into the playground.
30 props shown
categories*Nexerareadonly string[]The x labels, one per data point, as drawn on the axis ("Nov", "Dec",...). The first value of every series belongs to the first category. Time stays in the order you give: the plot does not sort.
categoryTitlesNexerareadonly string[]The long form of each category for the tooltip, the point labels and the table ("Aug 2026" for the axis label "Aug").
series*Nexerareadonly ChartSeries[]The lines. One series is the Figma Chart/Line/Trend (with the area under the line); two or more are Chart/Line/Comparison.
Each series gets its own colour, dash and marker shape (WCAG 1.4.1); the legend toggles them.
smoothNexerabooleanDraw a smooth monotone curve instead of straight segments. The curve never dips below or above the data it joins.
areaNexerabooleanFill the area between each line and the bottom of the plot.
markersNexera"active" | "all" | "none"Markers on the points: active shows the marker, halo and crosshair of the hovered or focused point; all draws a
marker on every point; none draws none. A point with no
neighbour is always marked.
referenceLinesNexerareadonly ChartReferenceLineSpec[]Horizontal reference lines: a target, a benchmark, an average. Each carries a text label, so it is not told by colour alone.
annotationsNexerareadonly ChartAnnotationSpec[]Callouts on data points. The text is also part of the point's label and tooltip.
yMinNexeranumberLower edge of the value axis.
yMaxNexeranumberUpper edge of the value axis.
includeZeroNexerabooleanStart the value axis at 0. A line chart shows change, so it need not; an area is easier to read from 0.
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:.
<LineChart className="shadow-sm" />Used in blocks
No block uses LineChart yet.
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- 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.