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
Installation
pnpm add @syntara/react @syntara/tokensimport { 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.
Negative values
Bars grow down from zero; the baseline stays square.
Visible table
showTable renders the data table under the chart.
Accessibility
| Keys | Action |
|---|---|
| Tab | Moves focus into the chart (one tab stop), to the last point you visited or the first. |
| ArrowRightorArrowLeft | Moves to the next / previous x position and shows its readout. Mirrored in right-to-left locales. |
| HomeorEnd | Jumps to the first / last x position. |
| Escape | Hides 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-labelledbyRequiredstringNames the chart. One is required; use aria-labelledby to point at a visible card title.
dataRequiredT[]One object per x position, in order.
xRequiredkeyof TKey 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").
heightnumberHeight of the plot plus the x axis, in px. The legend sits above it.
Default
240showGridbooleanHorizontal hairlines at each y tick.
Default
trueshowXAxisbooleanX labels, thinned to what fits (always the first and last).
Default
trueshowYAxisbooleanY tick labels in a gutter at the inline start.
Default
truehighlightunknownThe 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.
showLegendbooleanLegend above the plot.
Default
series.length > 1showTablebooleanShows the data table under the chart instead of keeping it visually hidden.
Default
falsexLabelstringHeader of the table's first column, e.g. "Month".
summarystringOne 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