Skip to content
Nizivo UI

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

OrdersOrders per day, Oct 17–30
Arrow keys move between bars; Home and End go to the first and last
DayOrders
Sat, Oct 17183
Sun, Oct 18214
Mon, Oct 19196
Tue, Oct 20241
Wed, Oct 21269
Thu, Oct 22232
Fri, Oct 23205
Sat, Oct 24228
Sun, Oct 25251
Mon, Oct 26279
Tue, Oct 27263
Wed, Oct 28299
Thu, Oct 29287
Fri, Oct 30313
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.

Net revenueChange per month, first half
Arrow keys move between bars; Home and End go to the first and last
Show data table
MonatNetto
Jan4.200 €
Feb2.600 €
Mar-1.800 €
Apr3.100 €
May-900 €
Jun5.400 €
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).

Orders
Loading chart
Orders

No data to show

Orders

This chart could not be loaded

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.

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

The y labels.

From CartesianChartOptions.

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<BarChartLabels>–
legendReactNode–

A <Legend>, or any node; rendered between the caption and the plot in every status.

From ChartFrameProps.

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 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.

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 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:

KeyDefault
categoryCategory
valueValue
missingNo value
interactioninteractive chart
hintArrow keys move between bars; Home and End go to the first and last
pointsChart bars

Other exports

BarChartLabels type

import type { BarChartLabels } from '@nizivo/charts/bar-chart'

BarChart's strings (§16.4), with ChartFrame's. Never concatenated: each is a whole label.

NameTypeDefaultDescription
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 title is not a string.

valuerequiredstring–

The series' name: the data table's value column and the tooltip row.

BarChartProps type

import type { BarChartProps } from '@nizivo/charts/bar-chart'

BarChart's props: the shared cartesian options (_core/api.ts; xTicks.max defaults to 8 here) and ChartFrame's, the <figure>'s attributes included.

The props of BarChart, listed above.

barChartLabels constant

import { barChartLabels } from '@nizivo/charts/bar-chart'

English defaults (§16.4); pass labels to 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):

ElementAttributes
svg.nz-bar-chart-gridaria-hidden: the grid lines and the zero line
div.nz-bar-chart-bandsaria-hidden: one column per datum, on the same tracks as the x labels
div.nz-bar-chart-banddata-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-tooltipWith 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-bardata-series="current", percentage y and height, rx="4"
div.nz-chart-interactionThe island's layer, with .nz-chart-interaction-columns: role="application", tabindex="0", aria-label, aria-roledescription, aria-describedby

CSS hooks

HookDefaultRead as
--nz-bar-chart-aspect32 / 13
  • aspect-ratio on .nz-bar-chart > .nz-chart-frame-body
--nz-bar-chart-bar-size1.375rem
  • inline-size on .nz-bar-chart-svg

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 (or labels.points), with the labels.interaction role description and the labels.hint description, rendered on the server. application stays 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 as labels.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.ListFormat writes in locale on the server. A pointer shows the card and says nothing.
  • The active column carries data-highlighted: the other bars dim to opacity 0.35, not chart/secondary (§14.1), and the active bar keeps chart/primary. The tooltip is ChartTooltip's card, beside the column's centre, and is aria-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.tsx and bar-chart-interaction.spec.tsx in packages/charts/test/browser check column mode in Chromium, WebKit and Firefox. With interaction="none" there is no card: the CSS hover tooltip does not meet 1.4.13.
  • Forced colours: bars draw in CanvasText (Highlight on the active column), grid lines in GrayText and the zero line in CanvasText; the card takes the system's Canvas and CanvasText, 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

KeysAction
TabFocuses the layer. A keyboard focus shows and speaks the active column, the last one at first.
ArrowRight / ArrowLeftThe 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 / EndThe first or last column.
EscapeHides 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.

On this page