DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Bar Chart

Token-driven bar chart built on Recharts v3 — vertical columns or horizontal bars, grouped, stacked or normalized to 100%, with tracks, per-bar colour, value formatting and a named accessible figure.

Playground

Installation

pnpm add @tessinaui/ui recharts

Usage

import { BarChart } from "@tessinaui/ui";
import type { ChartConfig } from "@tessinaui/ui";

const config: ChartConfig = {
  desktop: { label: "Desktop", color: "var(--chart-1)" },
  mobile: { label: "Mobile", color: "var(--chart-2)" },
};

const data = [
  { month: "Jan", desktop: 186, mobile: 80 },
  { month: "Feb", desktop: 305, mobile: 200 },
];

<BarChart
  data={data}
  config={config}
  categoryKey="month"
  label="Desktop traffic grew 64% in February"
/>

When to use one

  • Comparing a value across categories is what a bar chart is for. Bars encode length, which the eye compares more accurately than angle or area.
  • Long category names? Use orientation="horizontal". Vertical bars have to rotate or drop their labels; horizontal bars give each one a full line.
  • Ranking? Sort the rows before you pass them and use showTrack — a rail behind each bar makes "share of the leader" readable. There is no sort prop because Array.prototype.sort already exists.
  • Composition rather than volume? stacked="normalized" fills every bar to 100%, so the chart reads as proportion.
  • Below md, prefer stacked or small multiples over grouped. Grouped bars turn into unreadable hairlines at phone width — in a survey of 184 shipped mobile screens, grouped bars appeared exactly once.
  • Not a bar chart: a single value against a maximum is Progress or Meter; a value changing continuously over time is LineChart or AreaChart; parts of one whole with no time axis is PieChart.

Examples

Default

A single-series bar chart with upright columns.

Grouped

Multiple series side by side, one group per category. maxBarWidth keeps a few-category chart from drawing slabs.

Horizontal

Bars run sideways, which is the right choice for long category labels.

Stacked

Multiple series combined into one bar per category. Only the outermost segment rounds — a rounded join inside a stack reads as a notch — and adjacent segments are separated by a hairline in the chart surface colour, because chart colours are only required to contrast with the background, not with each other.

Normalized (100% stacked)

stacked="normalized" fills every bar to the full axis, so the chart reads composition instead of volume. The value axis formats itself as percentages.

Ranking with tracks

A neutral rail behind each bar, sorted rows, labels inside the bar end, and a minBarLength stub so a zero still reads as "zero" rather than "no data".

Labels

Each bar's numeric value rendered against the bar. labelPosition moves it outside the growing end, inside either end, or to the centre.

Per-bar colour and selection

barColor returns a fill per datum, so colour can encode meaning (over budget) rather than series identity. The same mechanism mutes the bars you did not select — there is no separate selection prop.

Negative values

Bars below zero grow downward, round their lower edge and place their labels outside that end. showZeroLine draws the baseline they cross.

Value formatting

One valueFormatter feeds the value axis, the bar labels and the tooltip, so a number cannot be spelled three different ways in the same chart.

Interactive legend

Legend interactivity is controlled: the chart never hides a series on its own. Hidden series stay in the legend as unpressed toggles, so they can be switched back on.

Target and reference lines

Reference lines, areas and dots compose as children. There is no prop per annotation kind, so anything Recharts can draw inside a plot is reachable.

Minimal

No value axis, no grid, no legend — bar height plus a sparse category axis carry the whole reading. categoryTickInterval decouples label count from bar count.

In a narrow card

A chart lives in whatever column a consumer gives it, and a 280px card is a tighter constraint than most phones. Drop the chrome the column cannot afford.

Accessibility

States

Loading, empty and error side by side.

Accessibility

  • label is the only way to name the chart. It renders the container as a role="figure" region with that accessible name. An aria-label on the root is silently ignored — the root carries no role for a label to attach to.
  • Say what the chart shows, not what it is. "Revenue more than doubled across the year" beats "Bar chart of revenue by quarter"; the reader can already tell it is a bar chart.
  • description renders a visually hidden summary inside the figure, so a static read carries the numbers without stepping through the bars.
  • Keyboard: Tab moves to the chart, ← / → step through categories and move the tooltip, and Escape dismisses the tooltip without moving focus (WCAG 1.4.13). Stepping again brings it back — dismissal is per reading.
  • Contrast: bars are held to 3:1 against the surface and tick text to 4.5:1, with gridlines exempt. Adjacent stacked segments carry a surface-coloured hairline, because chart colours are not required to contrast with each other.
  • A zero-height bar cannot be seen, hovered or focused. Set minBarLength when a zero is a meaningful value rather than missing data.
  • Colour is never the only channel. Pair barColor with a label, an ordering, or text near the chart.

API Reference

BarChart

PropTypeDefaultDescription
dataBarChartDatum[]—Row-per-category data.
configChartConfig—Series metadata (label, color).
categoryKeystring—Datum key for the category axis. Warns in development if it is not a key of the first row.
seriesstring[]all config keysWhich series to plot.
labelstring—Accessible name; renders the chart as a role="figure".
descriptionstring—Visually hidden summary for assistive tech.
orientation"vertical" | "horizontal""vertical"Columns vs sideways bars.
stackedboolean | "normalized"falseGrouped, stacked, or stacked to 100%.
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner rounding of the bar's growing end. full means a pill — both ends round.
size"sm" | "md" | "lg""md"Aspect ratio (3/1, 2/1, 16/9).
showTrackbooleanfalseFull-length neutral rail behind each bar.
minBarLengthnumber0Minimum drawn length in px, so a zero still renders.
maxBarWidthnumber—Cap on bar thickness in px.
barGapnumber4Gap between bars within a category group.
categoryGapnumber | string"10%"Gap between category groups.
showGridbooleantrueGridlines, always perpendicular to the bars.
showCategoryAxisbooleantrueThe category axis.
showValueAxisbooleanfalseThe numeric axis.
categoryAxisWidthnumber | "auto"estimatedCategory gutter width for horizontal charts.
categoryLabelAnglenumber—Rotate category tick labels (degrees).
categoryTickIntervalnumber | "preserveStartEnd" | …—Draw every n-th tick; decouples label count from bar count.
showZeroLinebooleanfalseEmphasise the zero baseline.
showLegendbooleantrueThe legend.
legendPosition"top" | "bottom""bottom"Legend placement.
hiddenSeriesstring[]—Series to hide. They stay in the legend as unpressed toggles.
onSeriesToggle(key: string) => void—Called when a legend item is clicked.
onBarClick(datum, index, seriesKey) => void—Called when a bar is clicked.
showLabelsbooleanfalseRender each bar's value as a label.
labelPosition"outside" | "inside-start" | "inside-end" | "center""outside"Where the value label sits.
valueFormatter(value, seriesKey) => string | number—Formats values in labels, the tooltip and the value axis.
categoryFormatter(value: string) => string—Formats category tick labels; truncation lives here.
barColor(datum, index, seriesKey) => string | undefined—Per-datum fill. Return undefined to keep the series colour.
showTooltipbooleantrueThe tooltip.
dir"ltr" | "rtl""ltr"Reading direction.
loadingbooleanfalseRender the skeleton, sized to the data.
errorReactNode—Render an error message with the error intent.
emptyStateReactNode"No data to display"Shown when data is empty.
childrenReactNode—Extra Recharts elements drawn in the plot (ReferenceLine, ReferenceArea, …).

Data attributes

AttributeOnValue
data-component="bar-chart"rootAlways. The base keeps data-slot="chart" on the same element, so target this to distinguish bar from the other five chart types.
data-chartrootGenerated chart id, used to scope the per-series CSS variables.
data-slot="chart-tooltip"tooltipWhile a tooltip is shown.
data-slot="chart-legend" / chart-legend-itemlegendLegend items are real aria-pressed buttons when onSeriesToggle is set.
data-slot="chart-skeleton"rootWhile loading. Also aria-busy="true".
data-slot="chart-message"rootWhile error or empty.

Agent notes

  • Name the chart with label, not aria-label. aria-label reaches the root element but the root has no role, so assistive tech ignores it. This is the single most common mistake with this component.
  • categoryKey must be a key of the data rows. A typo renders a chart with a blank category axis and warns in development.
  • stacked takes true or "normalized" — there is no percent value.
  • rounded is a token scale (none…full), not a pixel number. There is no radius prop; earlier documentation was wrong about this.
  • To highlight one bar, return a colour from barColor for it and a muted colour for the rest. There is no activeIndex prop.
  • To sort or cap the categories, transform data before passing it.

Theming

ElementToken
Bar fill--chart-1 … --chart-6 (via config[key].color)
Track railcurrentColor at 10%
Stacked segment separator--chart-surface
Grid + axis linesmuted-foreground/20 — not border, which collapses into the chart surface in dark mode
Axis tick labelsmuted-foreground
Value labelsforeground outside the bar, background inside it
Tooltip surfacepopover / popover-foreground / border

Migration

  • radius never existed. Earlier docs listed radius: number. The real prop is rounded, taking none | sm | md | lg | full, default md.
  • rounded="full" now renders a pill (both ends round). It previously rounded only the growing end, which domed a short wide bar rather than producing the pill shape the value implies.
  • Stacked bars round only their outermost segment. Every segment used to round, which put an arc inside the stack.
  • The skeleton now draws one placeholder bar per data row instead of a fixed seven.
  • error renders with the error intent instead of neutral chrome.
BannerBottom Nav

On this page

PlaygroundInstallationUsageWhen to use oneExamplesDefaultGroupedHorizontalStackedNormalized (100% stacked)Ranking with tracksLabelsPer-bar colour and selectionNegative valuesValue formattingInteractive legendTarget and reference linesMinimalIn a narrow cardAccessibilityStatesAccessibilityAPI ReferenceBarChartData attributesAgent notesThemingMigration