Skip to content

Bar Chart

Compares magnitudes across categories with vertical bars: rounded data ends, grouped series and an optional highlighted bar.

Preview

Card spending

$2,290 in October

6 categories. Spending: highest Sep ($3,050), lowest Jul ($1,920).

Card spending by month
MonthSpending
May$2,140
Jun$2,680
Jul$1,920
Aug$2,410
Sep$3,050
Oct$2,290

Installation

pnpm add @syntara/react @syntara/tokens
import { BarChart } from '@syntara/react';

Usage

import { BarChart } from '@syntara/react';

export function Spending({ data }: { data: { month: string; spend: number }[] }) {
  return <BarChart aria-label="Spending by month" data={data} x="month" highlight="Oct" series={[{ key: 'spend', label: 'Spending' }]} />;
}

Examples

Grouped

Two series side by side with a 2px surface gap and a legend.

  • Opened
  • Closed

5 categories. Opened: highest Support (142), lowest Security (23). Closed: highest Support (128), lowest Security (25).

Tickets opened and closed by team this month
TeamOpenedClosed
Support142128
Billing8691
Onboarding6452
Security2325
Platform7166

Negative values

Bars grow down from zero; the baseline stays square.

8 categories. Net cash flow: highest Apr (+£1,640), lowest May (-£980).

Net cash flow by month
MonthNet cash flow
Jan+£1,200
Feb-£450
Mar+£820
Apr+£1,640
May-£980
Jun+£310
Jul+£1,450
Aug-£220

Visible table

showTable renders the data table under the chart.

4 categories. Seats: highest Team (1,180), lowest Enterprise (310).

Seats by plan
PlanSeats
Starter420
Team1,180
Business760
Enterprise310

Accessibility

KeysAction
TabMoves focus into the chart (one tab stop), to the last point you visited or the first.
ArrowRightorArrowLeftMoves to the next / previous x position and shows its readout. Mirrored in right-to-left locales.
HomeorEndJumps to the first / last x position.
EscapeHides the readout without moving focus (WCAG 1.4.13). The next move shows it again.
  • The plot's hit layer is a React Aria ListBox (horizontal): one option per x position, named with its x and every series value, e.g. "Mar: Revenue $19,800, Costs $13,100". Focusing an option makes the screen reader read it, so no live region is needed.
  • The ListBox is named by aria-label or aria-labelledby (one is required) and described by a one-sentence summary (generated from the data, or the summary prop).
  • Every chart also renders its data as a <table> with a caption, visually hidden unless showTable. No value is only reachable by hovering.
  • The SVG, axis labels and the floating readout are aria-hidden: they repeat what the options and the table say.
  • Pointer, pen and touch snap to the nearest x position (full-height bands), so nobody has to land on a 2px line or a thin bar.
  • Text never wears a series colour: labels, axes, legend and readout use text.default and text.subtle; the series colour is only on marks and swatches. Series colours come from --syntara-chart-1..4, solved per brand and scheme to reach 3:1 on surfaces and stay distinguishable under colour-vision deficiency.
  • A 2px focus ring surrounds the plot and axes while an x position has keyboard focus.
  • Motion (line draw-in, area fade-up, bar growth, readout glide) sits behind prefers-reduced-motion: no-preference.
  • There are no disabled or invalid states: a chart is read-only.
  • Bars are at most 24px wide with a 4px rounded data end (the tenant's field radius when smaller) and square at the baseline; grouped bars are separated by a 2px gap, never an outline.

Guidelines

Do

  • Use highlight when one category is the story (this month, your plan).
  • Sort categories when order carries no meaning, so the comparison reads at a glance.
  • Use a stat tile instead when there is only one bar.

Don’t

  • Don't use a value ramp (darker for bigger) on unordered categories.
  • Don't plot more than 4 series in a group.
  • Don't label every bar; the axis, readout and table carry the values.

API reference

BarChart

aria-label | aria-labelledbyRequired
string

Names the chart. One is required; use aria-labelledby to point at a visible card title.

dataRequired
T[]

One object per x position, in order.

xRequired
keyof T

Key of the x value in each row (a month name, a Date, a number).

seriesRequired
{ key: string; label: string; color?: string }[]

What to plot, in palette order (--syntara-chart-1..4). Up to 4; past that, fold the tail into "Other". color overrides the palette with a token.

format
{ value?: Intl.NumberFormatOptions | fn; axis?: …; x?: Intl.DateTimeFormatOptions | fn }

Formats values (readout, table, names), y ticks (default: value in compact notation) and x values (Dates default to "12 Mar").

height
number

Height of the plot plus the x axis, in px. The legend sits above it.

Default 240

showGrid
boolean

Horizontal hairlines at each y tick.

Default true

showXAxis
boolean

X labels, thinned to what fits (always the first and last).

Default true

showYAxis
boolean

Y tick labels in a gutter at the inline start.

Default true

highlight
unknown

The x value of one category to emphasise: its bars keep the series colour, the rest turn border.strong (still 3:1 on the surface).

yDomain
[number, number]

Fixes the y range. Default: nice round numbers around the data, from 0 for bars.

showLegend
boolean

Legend above the plot.

Default series.length > 1

showTable
boolean

Shows the data table under the chart instead of keeping it visually hidden.

Default false

xLabel
string

Header of the table's first column, e.g. "Month".

summary
string

One sentence describing the data for screen readers. Default: generated from the data.

Tokens

The semantic tokens this component reads, grouped by what they control. Swatches show this site’s theme; change a tenant’s brand and the component follows with no code change.

Colour9
text.defaulttext.subtleborder.defaultborder.subtlesurface.raisedfocus.ringtext.brandaccent.textborder.strong
Type6
font.bodyfont.size.xsfont.size.smfont.tracking.xsfont.tracking.smfont.weight.semibold
Space and size4
space.1space.2space.3space.4
Shape3
radius.containerradius.fieldradius.pill
Depth3
hairlineshadow.overlayshadow.highlight
Motion5
motion.duration.fastmotion.duration.normalmotion.duration.slowmotion.easingmotion.easingOut
Other6
chart.1chart.2chart.3chart.4chart.gridchart.axis