Components · Charts
BarChart
Vertical and horizontal bar charts, grouped or stacked, with value labels, highlight, reference lines, keyboard tooltips, drill-in and a data table.
Grouped columns
Drafted and posted per week
| Week | Drafted | Posted |
|---|---|---|
| Week 1 | 12 | 8 |
| Week 2 | 9 | 9 |
| Week 3 | 15 | 11 |
| Week 4 | 11 | 10 |
| Week 5 | 14 | 13 |
| Week 6 | 18 | 12 |
import { BarChart } from '@rookcom/monarch-react/charts';
const WEEKS = [
{ week: 'Week 1', drafted: 12, posted: 8 },
{ week: 'Week 2', drafted: 9, posted: 9 },
{ week: 'Week 3', drafted: 15, posted: 11 },
{ week: 'Week 4', drafted: 11, posted: 10 },
{ week: 'Week 5', drafted: 14, posted: 13 },
{ week: 'Week 6', drafted: 18, posted: 12 },
];
<BarChart
title="Posts"
description="Drafted and posted per week"
data={WEEKS}
categoryKey="week"
categoryLabel="Week"
series={[
{ key: 'drafted', label: 'Drafted' },
{ key: 'posted', label: 'Posted' },
]}
locale="en-US"
/>Usage examples
Stacked with totals
import { BarChart } from '@rookcom/monarch-react/charts';
const WEEKS = [
{ week: 'Week 1', drafted: 12, posted: 8 },
{ week: 'Week 2', drafted: 9, posted: 9 },
{ week: 'Week 3', drafted: 15, posted: 11 },
{ week: 'Week 4', drafted: 11, posted: 10 },
{ week: 'Week 5', drafted: 14, posted: 13 },
{ week: 'Week 6', drafted: 18, posted: 12 },
];
<BarChart
title="Posts by state"
data={WEEKS}
categoryKey="week"
categoryLabel="Week"
layout="stacked"
valueLabels
series={[
{ key: 'posted', label: 'Posted' },
{ key: 'drafted', label: 'Drafted' },
]}
locale="en-US"
/>Highlighted month with a target
import { BarChart } from '@rookcom/monarch-react/charts';
const MONTHS = [
{ month: 'May', earned: 320 },
{ month: 'Jun', earned: 410 },
{ month: 'Jul', earned: 380 },
{ month: 'Aug', earned: 520 },
{ month: 'Sep', earned: 610 },
{ month: 'Oct', earned: 680 },
];
<BarChart
title="Earnings"
description="Last six months"
data={MONTHS}
categoryKey="month"
categoryLabel="Month"
series={[{ key: 'earned', label: 'Earned' }]}
highlightIndex={MONTHS.length - 1}
valueLabels
referenceLines={[{ value: 500, label: 'Goal' }]}
valueFormat={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }}
locale="en-US"
height={200}
/>Horizontal, with drill-in
import { BarChart } from '@rookcom/monarch-react/charts';
const CATEGORIES = [
{ topic: 'Billing and invoices', open: 42 },
{ topic: 'Account access', open: 31 },
{ topic: 'Integrations', open: 18 },
{ topic: 'Feature requests', open: 12 },
{ topic: 'Other', open: 7 },
];
<BarChart
title="Open requests by topic"
data={CATEGORIES}
categoryKey="topic"
categoryLabel="Topic"
orientation="horizontal"
valueLabels
series={[{ key: 'open', label: 'Open' }]}
onBarSelect={() => undefined}
locale="en-US"
/>Purpose
Compares amounts across categories or discrete periods, one or more series per category, with exact values on hover, tap or arrow keys and the full data as a table.
Use when
- Comparing categories: requests per topic, revenue per month, posts per week.
- Showing parts of a total per category (
layout="stacked"). - Long category names or rankings (
orientation="horizontal").
Avoid when
- A continuous trend with many points: use LineChart.
- A simple ranked list with values and links: use BarList.
- Shares of one whole: use StackedProportionBar or DonutChart.
Design principles
- Bars always start at zero; the value axis can only fix its far end.
- Only the far end of a bar is rounded (4px), stacked segments are separated by a surface gap, and only the outer segment is rounded.
- Colour encodes the series, never the category; one series is one colour, and
highlightIndexemphasises by dimming the rest. - Value labels are optional and compact (1.2K); exact values live in the tooltip and table.
API reference
import { BarChart } from '@rookcom/monarch-react/charts';| Export | Kind | Rendering | Root class |
|---|---|---|---|
BarChart | component | client | .mn-chart |
Renders ChartFrame (ref to its <figure>); ChartFrame props pass through except children, legend and table, which BarChart builds.
data is one object per category; categoryKey names the category field and each series key a numeric field.
height defaults to 240 for vertical charts; horizontal charts grow by 32px per category.
Generated from the TypeScript declarations of packages/react/src/components/charts/bar-chart.
Properties
BarChart
Bar and column chart: one or more series per category, grouped or stacked, vertical or horizontal, with value labels, a highlighted category, reference lines, tooltips on hover, touch and keyboard, legend toggles, drill-in and an accessible data table.
| Prop | Type | Description |
|---|---|---|
categoryKey required | string | Field holding the category: a label, a number or a `Date` (formatted with `categoryFormat`). |
data required | readonly ChartDatum[] | One row per category, in display order, with one number per series. Missing values draw no bar. |
series required | readonly ChartSeries[] | The measures to draw. Colours follow the order unless pinned. |
title required | ReactNode | The chart's name. Labels the figure. Required. |
actions | ReactNode | Period or metric controls in the header, such as a pill TabsFilter or a Select. |
activeIndex | number | null | Controlled inspected category (`null` for none). |
categoryFormat | Intl.DateTimeFormatOptions | `Intl.DateTimeFormat` options when categories are dates. Default `{ month: 'short', day: 'numeric' }`. |
categoryLabel | string | Column header for the categories in the data table. Default: `categoryKey`. |
className | string | |
defaultHiddenSeries | readonly string[] | Uncontrolled initial hidden series. |
defaultShowTable | boolean | Uncontrolled initial table view. Default `false`. |
description | ReactNode | One line under the title: the period, unit or what the chart answers. Describes the figure. |
emptyActions | ReactNode | Actions in the empty state, such as a link to set up tracking. |
emptyDescription | ReactNode | Empty-state description, such as what will appear and when. |
footer | ReactNode | Notes under the chart: source, last updated, a footnote. |
formatCategory | ((value: XValue, index: number) => string) | Formats categories everywhere. Wins over `categoryFormat`. |
formatTick | ValueFormatter | Formats value-axis ticks. Default: compact notation of `valueFormat`. |
formatValue | ValueFormatter | Formats values in the tooltip, table, labels and summary. Wins over `valueFormat`. |
headingLevel | ChartFrameHeadingLevel | Render the title as a heading (`h2`–`h6`) to match the page outline. Default: not a heading. |
height | number | Height of the chart in px. Default 240 vertical; horizontal grows with the number of categories (32px each). |
hiddenSeries | readonly string[] | Controlled list of hidden series keys. |
highlightIndex | number | Emphasise one category (the current month, say); the others are dimmed. |
id | string | |
labels | BarChartLabels | |
layout | BarChartLayout | With several series: side by side (`grouped`, default) or one stacked bar per category. Exposed as `data-layout`. |
legendToggle | boolean | Legend entries toggle their series. Default `true` (legend shows for two or more series). |
locale | string | BCP 47 locale for numbers and dates. Pass it when rendering on the server. |
onActiveIndexChange | ((index: number | null) => void) | |
onBarSelect | ((datum: ChartDatum, index: number) => void) | Click, Enter or Space on the inspected category, for drill-in (open the filtered list). |
onHiddenSeriesChange | ((hidden: string[]) => void) | |
onRetry | (() => unknown) | Error-state retry. Return the refetch promise to keep the button busy until it settles. |
onShowTableChange | ((showTable: boolean) => void) | |
orientation | BarChartOrientation | `vertical` (default) columns, or `horizontal` bars for long category names and rankings. Exposed as `data-orientation`. |
referenceLines | readonly ChartReferenceLine[] | Dashed lines across the bars for targets or averages. |
showTable | boolean | Controlled table view. |
status | ChartFrameStatus | `ready` (default) shows the chart; `loading` a skeleton with `aria-busy`; `empty` an empty state; `error` an error with Try again when `onRetry` is set. Exposed as `data-status`. |
summary | string | Replaces the generated text summary (the plot's accessible name). |
tableToggle | boolean | Show the "View as table" toggle. Default `true` when `table` is set. |
valueDomain | readonly [ChartDomainBound, ChartDomainBound] | Fix the far end of the value axis (bars always start at zero). |
valueFormat | Intl.NumberFormatOptions | `Intl.NumberFormat` options for values in the tooltip, table, labels and summary. |
valueLabels | boolean | Print each bar's value (the stack total when stacked) at its end. |
Variants
| Prop | Values | Description |
|---|---|---|
orientation | 'vertical''horizontal' | Columns, or bars with category labels on the left (truncated with an ellipsis). data-orientation. |
layout | 'grouped''stacked' | Series side by side, or one bar per category with positive and negative parts stacked separately and a Total column in the table. data-layout. |
valueLabels | 'true''false' | Each bar’s value (the stack total when stacked) at its end. |
series[].dashed | 'true''false' | An outlined bar instead of a filled one, as a second encoding (a forecast, a target). |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Inspecting | – | A muted band behind the category and a tooltip with every visible series (and the total when stacked). |
| Highlighted | data-dimmed on the other bars | With highlightIndex, every other category is dimmed. |
| Selectable | data-selectable | With onBarSelect the plot shows a pointer cursor and Enter selects. |
| Series hidden | aria-pressed="false" | The series leaves the plot and the axis; it stays in the table. |
| Empty / loading / error | data-status | From ChartFrame. |
Interaction behaviour
- Hover or tap a category to inspect it; leaving with the mouse clears it.
onBarSelect(datum, index)runs on click, Enter or Space, for drill-in such as opening a filtered list.- Legend entries toggle series (controlled
hiddenSeriesor uncontrolleddefaultHiddenSeries). activeIndex+onActiveIndexChangesync the inspected category with other charts.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Focuses the plot (one tab stop) and inspects the first category. |
| ArrowRight / ArrowDown | Next category (either pair works in both orientations). |
| ArrowLeft / ArrowUp | Previous category. |
| Home / End | First or last category. |
| PageUp / PageDown | Moves a tenth of the categories. |
| Escape | Hides the tooltip. |
| Enter / Space | Calls onBarSelect for the inspected category. |
Accessibility
- Role
- figure (ChartFrame); the plot is a group with aria-roledescription "chart"
- Accessible name
- The figure by
title; the plot by a generated summary (count, then total and highest category per series) orsummary.
- The SVG is
aria-hidden; the summary, a polite live region during keyboard inspection and the data table carry the values. - The tooltip is visual only, so pointer use does not flood screen readers.
- The data table has every series and, when stacked, a Total column.
- Emphasis is not colour alone: the highlighted category keeps full opacity while others dim, and value labels can show the numbers.
- All text, including summaries and "Total", is translatable through
labels.
Responsive behaviour
- Lays out at the measured container width (works at 320px); bar thickness is capped so few bars do not turn into slabs.
- Vertical category labels thin out when they would collide; horizontal labels truncate to at most 40% of the width.
- Server render and first paint use a 640px layout scaled to fit, matching on hydration.
Theme support
- Series use
--mn-color-data-1…-5; stacked segments are separated by a--mn-color-bg-surfacestroke. - Light, dark and high contrast follow the tokens; data colours meet 3:1 (4.5:1 in high contrast) on every chart background.
- Dimming uses opacity with a fast, token-timed transition that is instant under reduced motion.
Do and don't
Do
- Sort categories meaningfully: by time for periods, by value for rankings.
- Use horizontal bars when labels are longer than a word or two.
- Fill missing periods with zero in your data when zero is the truth; leave them out when it is not.
Don't
- Colour bars by category or by value; one series is one colour.
- Stack more than about four series; the inner segments become unreadable.
- Start the value axis anywhere but zero (BarChart does not allow it).
Related components
- ChartFrame: The frame BarChart renders.
- LineChart: Continuous trends with many points.
- BarList: A ranked list of labelled values with links, no axes.
- StackedProportionBar: Shares of a single total in one bar.
- Sparkline: A tiny sparkbar (
variant="bar") inside a cell or card.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | ChartFrame |
Hand-written SVG from src/internal/charts; no charting library.
Testing coverage
- Grouped bars per category, legend toggles and a generated summary; no axe violations.
- Stacked: Total column and tooltip footer, only the outer segment rounded.
- Keyboard inspection with live announcements; Enter and Space call
onBarSelect; band highlight. - Tap and click select a category.
- Horizontal layout: category labels, value labels, auto height, no legend for one series.
highlightIndexdims the other categories.- Negative values extend below zero.
- Hiding a series keeps the other colour.
- Locale-aware percent formatting.
- Renders on the server.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/bar-chart; Cross-browser axe: /components/bar-chart |
Tests: packages/react/src/components/charts/bar-chart