BarChart
Counts per period, drawn on the server. Bars cap at 22 px, round only at the data end and sit under a value axis rounded out to whole ticks; one keyboard stop steps through the columns and shows each bar's tooltip card.
Import
import { BarChart } from '@nizivo/charts/bar-chart';Static or interactive
Split. BarChart's bars, grid, tick labels and data table are server HTML. Its module imports
one client module, the ChartInteraction island, which it renders in column mode 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: hovering a column dims the other bars and shows
that bar's own tooltip in CSS alone, which is hover-only and cannot be dismissed with Escape, so it
does not meet WCAG 1.4.13. The data table is then the screen-reader path; pass
tableView="details" so sighted keyboard users can reach the values too.
As with AreaChart, the shell imports its leaf:
Turbopack and next dev ship a client module that a page's server code imports, rendered or not,
so interaction="none" alone does not keep the island out of a Turbopack page. That follows from
AreaChart's /chart-area-static measurement in the Next.js fixture, which has no BarChart page
yet. This page has its
own route, so the island loads here and on the AreaChart page, and on no other page of this site.
How to choose.
Examples
Basic
import type { JSX } from 'react';
import { BarChart } from '@nizivo/charts/bar-chart';
interface Day {
date: string;
orders: number;
}
// Figma 03's bar chart: orders per day, Oct 17–30. The last bar (313) is above Figma's top tick
// (300); here the domain is niced first, so the axis ends at 400.
const ORDERS: Day[] = [183, 214, 196, 241, 269, 232, 205, 228, 251, 279, 263, 299, 287, 313].map(
(orders, i) => ({ date: `2026-10-${String(17 + i)}`, orders }),
);
export function BarChartExample(): JSX.Element {
return (
<BarChart
title="Orders"
summary="Orders per day, Oct 17–30"
data={ORDERS}
x={(d) => d.date}
y={(d) => d.orders}
dateFormat={{ day: 'numeric' }}
labels={{ category: 'Day', value: 'Orders' }}
/>
);
}
Negative
Negative values hang from the zero baseline, rounded at their data end too; values in euros.
import type { JSX } from 'react';
import { BarChart } from '@nizivo/charts/bar-chart';
interface Month {
month: string;
net: number;
}
const NET: Month[] = [
{ month: 'Jan', net: 4200 },
{ month: 'Feb', net: 2600 },
{ month: 'Mar', net: -1800 },
{ month: 'Apr', net: 3100 },
{ month: 'May', net: -900 },
{ month: 'Jun', net: 5400 },
];
export function BarChartNegativeExample(): JSX.Element {
return (
<BarChart
title="Net revenue"
summary="Change per month, first half"
data={NET}
x={(d) => d.month}
y={(d) => d.net}
locale="de-DE"
format={{ style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }}
labels={{ category: 'Monat', value: 'Netto' }}
tableView="details"
/>
);
}
Status
Loading, empty and error keep the frame's box (§16.5).
import type { JSX } from 'react';
import { BarChart } from '@nizivo/charts/bar-chart';
interface Day {
date: string;
orders: number;
}
// Figma 03's bar chart: orders per day, Oct 17–30. The last bar (313) is above Figma's top tick
// (300); here the domain is niced first, so the axis ends at 400.
const ORDERS: Day[] = [183, 214, 196, 241, 269, 232, 205, 228, 251, 279, 263, 299, 287, 313].map(
(orders, i) => ({ date: `2026-10-${String(17 + i)}`, orders }),
);
export function BarChartStatusExample(): JSX.Element {
return (
<div>
<BarChart
title="Orders"
status="loading"
data={[]}
x={(d: Day) => d.date}
y={(d) => d.orders}
/>
<BarChart title="Orders" data={[]} x={(d: Day) => d.date} y={(d) => d.orders} />
<BarChart title="Orders" status="error" data={ORDERS} x={(d) => d.date} y={(d) => d.orders} />
</div>
);
}
Props
Server component. Props: BarChartProps<T>. Source: src/bar-chart/bar-chart.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
axisFormat | Intl.NumberFormatOptions | `format` in compact notation with at most one fraction digit ("$8K") | The y labels. From |
datarequired | readonly T[] | – | In x order: one datum per point or bar. From |
dateFormat | Intl.DateTimeFormatOptions | { month: 'short', day: 'numeric' } ("Oct 1") | ISO plain dates on the x axis. From |
empty | ReactNode | – | Replaces the default empty message. From |
error | ReactNode | – | Replaces the default error message, for example an InlineError with a retry action. From |
format | Intl.NumberFormatOptions | Intl.NumberFormat's own ({}) | Values in the tooltip and the data table. From |
interaction | ChartInteractionMode"nizivo""none" | 'nizivo' | From |
labels | Partial<BarChartLabels> | – | |
legend | ReactNode | – | A From |
locale | string | 'en-US' | The BCP 47 locale every number and date is formatted in, once, on the server (§16.4). From |
longDateFormat | Intl.DateTimeFormatOptions | { weekday: 'short', month: 'short', day: 'numeric' } | ISO plain dates in the tooltip and the data table. From |
pending | boolean | – | A refresh is in flight while the chart shows data (§16.5): From |
render | RenderProp<ChartFrameState> | – | From |
status | DataStatus"ready""loading""empty""error" | 'ready'; a ready chart with no table rows renders as `'empty'`. | From |
summary | ReactNode | – | One line under the title, for example "Daily sales, Oct 1–30, vs previous period". From |
tableView | ChartTableView"hidden""details" | 'hidden' | From |
titlerequired | ReactNode | – | The figure's caption and its accessible name. From |
titleVisibility | ChartTitleVisibility"visible""hidden" | 'visible' |
From |
xrequired | Accessor<T, string> | – | The x value: an ISO plain date ( From |
xDirection | ChartXDirection"ltr""rtl""auto" | 'ltr' | From |
xTicks | ChartXTicks | – | From |
yrequired | Accessor<T, number | null | undefined> | – | The value. A value that is not a finite number is a gap (no point, no bar). From |
yTicks | number | 4 | About how many y labels; the value domain is niced for this count. From |
- Also takes every
<figure>attribute exceptchildren,title.
render takes an element, or, in client code, a function of the props and this state (ChartFrameState):
| Name | Type | Default | Description |
|---|---|---|---|
interactionrequired | ChartInteractionMode"nizivo""none" | – | As passed: whether the child claims keyboard access to its points. |
pendingrequired | boolean | – | |
statusrequired | DataStatus"ready""loading""empty""error" | – | The status as rendered: |
x and y 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. A y that is not a
finite number draws no bar and shows labels.missing in the table and the card; zero draws no
bar; a negative value hangs from the zero line. format defaults to Intl.NumberFormat's own
options ({}), as in every chart: a fractional value shows its fraction. axisFormat defaults to
format in compact notation with at most one fraction digit.
xTicks (ChartXTicks): at most max x labels (BarChart: 8), the first and last always shown;
every sets the bars between labels. A label sits centred under its bar; the first and last, when
wider than their bar's band, are clamped inward to the plot's edges.
| Name | Type | Default | Description |
|---|---|---|---|
every | number | – | Data points (or bars) between labels; the last one always has a label. Derived from |
max | number | – | At most this many labels, the first and last included. AreaChart: 6, BarChart: 8. |
The props marked as from CartesianChartOptions are the ones BarChart and
AreaChart share, with the same names and types (one API shape across
the charts, M40). The defaults are the same too, except xTicks.max: 8 here, 6 for AreaChart.
labels takes Partial<BarChartLabels>, which extends ChartFrameLabels (its members are under
Other exports). The English defaults, in barChartLabels, on top of
ChartFrame's:
| Key | Default |
|---|---|
category | Category |
value | Value |
missing | No value |
interaction | interactive chart |
hint | Arrow keys move between bars; Home and End go to the first and last |
points | Chart bars |
Other exports
BarChartLabelstypeimport type { BarChartLabels } from '@nizivo/charts/bar-chart'BarChart's strings (§16.4), with ChartFrame's. Never concatenated: each is a whole label.
Name Type Default Description categoryrequiredstring– The data table's first column header (the x values).
hintrequiredstring– The interactive layer's keyboard instructions.
interactionrequiredstring– The interactive layer's role, as assistive technology names it.
missingrequiredstring– The data table cell (and the tooltip value) of a datum with no finite value.
pointsrequiredstring– The interactive layer's name when
titleis not a string.valuerequiredstring– The series' name: the data table's value column and the tooltip row.
BarChartPropstypeimport type { BarChartProps } from '@nizivo/charts/bar-chart'BarChart's props: the shared cartesian options (
_core/api.ts;xTicks.maxdefaults to 8 here) and ChartFrame's, the<figure>'s attributes included.The props of
BarChart, listed above.barChartLabelsconstantimport { barChartLabels } from '@nizivo/charts/bar-chart'English defaults (§16.4); pass
labelsto translate per instance.const barChartLabels: BarChartLabels
Data attributes and slots
Inside the ChartFrame root, which carries
nz-bar-chart (with axes="frame" and band-aligned x labels):
| Element | Attributes |
|---|---|
svg.nz-bar-chart-grid | aria-hidden: the grid lines and the zero line |
div.nz-bar-chart-bands | aria-hidden: one column per datum, on the same tracks as the x labels |
div.nz-bar-chart-band | data-slot="bar-chart-band"; data-highlighted on the active column while the island shows its card (never in the server HTML) |
span.nz-bar-chart-tooltip | With interaction="none" only: the band's CSS hover tooltip (title, mark and value); .nz-bar-chart-band-end on the second half's bands, whose tooltip opens toward the start |
rect.nz-bar-chart-bar | data-series="current", percentage y and height, rx="4" |
div.nz-chart-interaction | The island's layer, with .nz-chart-interaction-columns: role="application", tabindex="0", aria-label, aria-roledescription, aria-describedby |
CSS hooks
| Hook | Default | Read as |
|---|---|---|
--nz-bar-chart-aspect | 32 / 13 |
|
--nz-bar-chart-bar-size | 1.375rem |
|
Layers: nz.components. Classes: .nz-bar-chart, .nz-bar-chart-band, .nz-bar-chart-band-end, .nz-bar-chart-bands, .nz-bar-chart-bar, .nz-bar-chart-grid, .nz-bar-chart-svg, .nz-bar-chart-tooltip, .nz-bar-chart-tooltip-mark, .nz-bar-chart-tooltip-title, .nz-bar-chart-tooltip-value, .nz-chart-baseline, .nz-chart-frame-body, .nz-chart-grid.
Tokens this stylesheet reads (17)
--nz-bg-surface-raised, --nz-border-subtle, --nz-border-width-default, --nz-chart-primary, --nz-duration-chart-draw, --nz-duration-fast, --nz-easing-standard, --nz-elevation-3, --nz-font-body-m, --nz-font-label-m-strong, --nz-font-numeric-m, --nz-radius-full, --nz-radius-md, --nz-space-2, --nz-space-3, --nz-text-primary, --nz-text-secondary
--nz-bar-chart-aspect is the body's aspect ratio (Figma 03's 640 × 260), in place of ChartFrame's,
and --nz-bar-chart-bar-size the widest a bar gets (22 px); a bar is at most 52 % of its column.
Accessibility
- The bars and the grid are
aria-hidden; the server-rendered data table (one row per datum) is the accessible alternative. By default it is visually hidden and has no Tab stop, so it serves screen readers; the island is the keyboard path to each bar. - With the island, the plot is one Tab stop: a
role="application"layer named by the title (orlabels.points), with thelabels.interactionrole description and thelabels.hintdescription, rendered on the server.applicationstays until the manual screen-reader pass (M40). The arrow keys step one column at a time, every datum included (a datum with no finite value is still a column, read aslabels.missing). - One polite live region, empty in the server HTML, speaks the column a key move reaches: its
title, the series' name and the value (the Basic example's last column: "Fri, Oct 30, Orders,
313"), joined with the
separators
Intl.ListFormatwrites inlocaleon the server. A pointer shows the card and says nothing. - The active column carries
data-highlighted: the other bars dim to opacity 0.35, notchart/secondary(§14.1), and the active bar keepschart/primary. The tooltip is ChartTooltip's card, beside the column's centre, and isaria-hidden. - Against WCAG 1.4.13 (Content on Hover or Focus), the card is dismissable: Escape hides it and
clears the highlight wherever focus is (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 follows the band under the pointer, so it cannot be held on
one bar: not "hoverable" in 1.4.13's sense.
chart-interaction-columns.spec.tsxandbar-chart-interaction.spec.tsxinpackages/charts/test/browsercheck column mode in Chromium, WebKit and Firefox. Withinteraction="none"there is no card: the CSS hover tooltip does not meet 1.4.13. - Forced colours: bars draw in
CanvasText(Highlighton the active column), grid lines inGrayTextand the zero line inCanvasText; the card takes the system'sCanvasandCanvasText, as every ChartTooltip does. Only Chromium's forced-colours emulation has been looked at. - No stagger in the reveal: a per-bar delay would need an inline style. All bars grow together over
--nz-duration-chart-draw, 0 ms under Reduced motion.
Keyboard
| Keys | Action |
|---|---|
| Tab | Focuses the layer. A keyboard focus shows and speaks the active column, the last one at first. |
| ArrowRight / ArrowLeft | The next or previous column on screen; on a mirrored plot (xDirection="rtl", or "auto" on a right-to-left page) ArrowRight goes back in time. |
| Home / End | The first or last column. |
| Escape | Hides the card and clears the highlight, wherever focus is. |
Source: chart-interaction.client.tsx in packages/charts/src/_island (column mode). Touch: a tap
shows the column's card and keeps it after the finger lifts (synthetic pointer events only; real
touch hardware is not tested).
Figma
Bar Chart, 7275:13387: State=Default 7275:13388 and State=Hover 7275:13419, 640 × 260.
"Counts per period. Bars cap at 22px, round only at the data end, and dim their neighbours on
hover." Under the island (the default) the tooltip is Figma 03's fixed-width Chart Tooltip card; with
interaction="none" the CSS tooltip hugs its content ("Orders 299"), as in week 10. Two differences, on
purpose: Figma's last bar (313) rises above its top tick (300), while code rounds the axis out
first, so it ends at 400; and Figma dims to chart/secondary, code by opacity. Figma's crowded
"29 30" labels become "27 … 30". Notes: docs/figma/03-data-viz.md §4.5.
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.
DonutChart
Share of a whole, drawn on the server. At most three categories plus "Other", in fixed colours that never cycle, with a legend of values and shares beside the ring.