Content & Data
Bar Chart
Bar Chart compares categories with vertical or horizontal bars — a single series by default, or several series grouped, stacked, or stacked to 100%. Static and non-tabbable, with the data also exposed as a hidden table.
Part of the chart components — see the Charts overview for how the families and dashboard compositions fit together.
Single component — orientation and stacking are props, not variants
- React last updated
- 2026-09-22
- Documentation last updated
- 2026-09-26
- Accessibility target
- WCAG 2.2 AA (target)
- Version
- 0.1.0-beta
Known open questions
- Figma covers only the single-series Bar Chart (example) static visual reference (bars + category labels); it is not a reusable Figma component. Multiple series, the value axis, grid, legend and tooltip have no Figma reference — they use current Skrewww tokens conservatively and need a design decision before parity can be claimed.
- Horizontal orientation, stacking modes, the value axis, grid and tooltip likewise have no Figma reference; the Figma reference bars use radius/xs, but bar spacing and the React corner radius are this implementation's own decisions.
- The categorical series palette (four slots: brand action, primary text, amber, green) is a CH-2 implementation choice checked for >= 3:1 contrast on the default white surface, not a Figma-verified palette. Slots carry no status meaning; positive/negative colors are a separate, unresolved design decision.
- An interactive legend (series toggling) is deferred: it needs a hidden-series state plus keyboard and announcement design. The legend is a non-interactive key.
- The tooltip is pointer/touch-only supplementary information; every value is also in the hidden table. A keyboard-operable chart (focusable points, arrow-key navigation) is deferred.
- Dense data and long category labels have no policy yet: category labels always render (no tick skipping, rotation, truncation, scrolling or aggregation), so many points or long labels can collide or clip. A density and label-length policy is an open decision.
- The horizontal category axis has a fixed width; long labels are not truncated or wrapped and can clip.
Purpose
Anatomy
Variants and states
When to use
When not to use
Accessibility
Common mistakes
Properties
Why ResponsiveContainer instead of fixed pixel dimensions?
Why is there no Y-axis, grid, legend or tooltip by default?
How is the underlying data exposed to screen readers?
Why is there no HorizontalBarChart, StackedBarChart or PercentStackedBarChart?
Stacked or percent?
Tokens used
Component API
| Prop | Type | Default | Description |
|---|---|---|---|
| data | ({ label: string } & { [seriesKey: string]: string | number | null })[] | — | One row per category: { label, value } for a single series, or { label, [series.key]: number | null } when `series` is given. null means no data (a gap or no bar). Duplicate labels are allowed. |
| label | string | — | Accessible name for the chart — also used as the hidden data table's caption. |
| height | number | 240 | Fixed pixel height. Width is fluid (ResponsiveContainer), filling the parent. |
| series | { key: string; label: string; color?: "series-1" | "series-2" | "series-3" | "series-4"; format?: ChartValueFormat }[] | — | Series definitions: { key, label, color?, format? }. `key` is the row property holding the value (unique, never "label"); `color` is a slot "series-1"…"series-4" (default: by position, wrapping after four). Omit for a single series read from each row's `value`. |
| orientation | "vertical" | "horizontal" | "vertical" | "vertical" draws columns; "horizontal" draws bars growing left to right with the category labels on the left. |
| stacking | "none" | "stacked" | "percent" | "none" | "none" groups series side by side; "stacked" stacks them into one bar per category; "percent" stacks to 100% of each category. |
| legend | boolean | — | Show a legend. Defaults to true when there is more than one series. It is a non-interactive key (no series toggling). |
| tooltip | boolean | — | Show a hover/touch tooltip listing the category and each series' value. Defaults to false. Supplementary only: every value is also in the hidden table. |
| showCategoryAxis | boolean | — | Show the category axis labels. Defaults to true. |
| showValueAxis | boolean | — | Show the value-axis ticks. Defaults to false. |
| showGrid | boolean | — | Show grid lines. Defaults to false. |
| valueFormat | ChartValueFormat | — | How values are formatted on value-axis ticks, in the tooltip, and in the hidden table: a function (value: number) => string, or { kind: "number" | "compact" | "percent" | "currency", ... }. Default: the raw number. A series' own `format` overrides it. |
| labelFormat | ChartLabelFormat | — | How category labels are formatted on the axis, in the tooltip, and in the hidden table: a function (label: string) => string, or { kind: "date", granularity: "day" | "month" | "year" } for normalized ISO date labels (UTC). Default: the label as given. |
React example
import { BarChart } from "@/components/ui";
const monthlySignups = [
{ label: "Jan", value: 58 },
{ label: "Feb", value: 95 },
{ label: "Mar", value: 76 },
{ label: "Apr", value: 128 },
{ label: "May", value: 108 },
{ label: "Jun", value: 140 },
];
const quarterly = [
{ label: "Q1", revenue: 120, orders: 80 },
{ label: "Q2", revenue: 150, orders: 95 },
{ label: "Q3", revenue: 135, orders: 110 },
];
export function Example() {
return <BarChart data={monthlySignups} label="Monthly signups" />;
}
export function MultiSeriesExample() {
return (
<BarChart
data={quarterly}
label="Revenue and orders by quarter"
series={[
{ key: "revenue", label: "Revenue" },
{ key: "orders", label: "Orders" },
]}
stacking="stacked"
showValueAxis
showGrid
tooltip
/>
);
}