Bar chart React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
Compares categories or periods as columns or horizontal bars, grouped, stacked or 100%, with negatives below a clear zero line.
The bars grow out of the zero line the first time the chart scrolls into view, one category a beat after the other; value labels fade in after their bar. Move the mouse over the plot and the whole column (or row, when horizontal) under it lights up with a soft band while a readout lists every visible series in it (stacked charts add a total, 100% charts add each share). On a phone, tap a column to pin the readout above the plot, drag sideways to scrub (a vertical drag still scrolls the page), tap it again or anywhere outside to clear it. With the keyboard, Tab to the plot: Left / Right walk the categories (Up / Down for horizontal bars), Home / End jump to the ends, PageUp / PageDown move by ten, Enter fires onDatumClick for the first visible series and Escape clears; a live region reads each category. Click or tap a legend entry to hide its series (the rest regroup and the axis rescales with a glide), hover or focus one to highlight it. Switching between grouped, stacked and 100% glides every bar to its new place.
Bar chart is a Pro React component in the React chart and stat components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add bar-chart, together with Chart kit, Motion presets, UIProvider and useLocale and the npm package motion.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add bar-chartBar chart is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import BarChart from @/components/ui/bar-chart.
import { BarChart } from "@/components/ui/bar-chart"
const data = [
{ month: "2026-07", collected: 106300, expenses: 72400 },
{ month: "2026-08", collected: 97500, expenses: 69900 },
{ month: "2026-09", collected: 128450, expenses: 81200 },
]
export function MonthlyCollections() {
return (
<BarChart
data={data}
xKey="month"
series={[
{ key: "collected", label: "Collected" },
{ key: "expenses", label: "Expenses" },
]}
valueFormat="currency"
currency="USD"
label="Collected vs. expenses per month"
/>
)
}Props
data*ChartDatum[]One row per category, in drawing order: { month: "2026-09", collected: 128450, expenses: 81200 }. Negative values grow down (or left) from zero.
xKeydefault"date"stringThe row key for the categories. ISO days ("2026-09-24") and months ("2026-09") print as local dates; other strings print as they are.
seriesBarChartSeries[]Which keys to draw, in color and stack order: { key, label?, color?, name? }. Inferred from the numeric keys of the first rows when omitted.
colorsstring[]Per-position palette overrides: a slot ("series-3"), a tone ("neutral", "success") or any CSS color.
heightdefault240numberPlot height in px, reserved before the chart measures so nothing jumps.
orientationdefault"vertical""vertical" | "horizontal"horizontal puts the categories in a label column on the left (at most 40% of the width, long names end in an ellipsis; the tooltip and table keep the full text). The better choice on phones with long names.
typedefault"grouped""grouped" | "stacked" | "percent"grouped sets the series side by side, stacked piles them (negatives below zero), percent piles their shares to 100%.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"Corner radius of the data end: 0 / 2 / 4 / 6px or half the thickness. The base stays square; stacks round only their outermost segment.
barSizedefault24numberMaximum bar thickness in px. Bars shrink to fit narrow plots instead of overlapping.
barGapdefault2numberPx between the bars of one group and between stacked segments, painted in the surface color.
categoryGapdefault0.3numberThe share of each category's band left empty between groups.
showValuesdefaultfalsebooleanA value at each bar's tip (grouped), the total at each stack's end (stacked) or the share inside each segment (percent). Every label uses one form: full amounts when they all fit, else the compact form; when they still collide every k-th category is labelled, counted back from the newest. A label with no room goes inside its bar or is skipped, never clipped; bars thinner than 14px carry none.
getBarColor(datum: ChartDatum, series: ResolvedChartSeries, index: number) => (string | undefined)A color per bar (a slot, a tone or any CSS color): "danger" for negatives, "brand" for the current month. undefined keeps the series color.
highlightLastdefaultfalsebooleanDims every category but the last to 45%, for the current-period look. The inspected category lights up too.
valueFormatdefault"number"ChartValueFormat = "number" | "compact" | "currency" | "percent"How values print in the tooltip, ticks, value labels and table. Percent takes whole numbers (12.4 -> "12,4 %").
currencydefault"USD"stringISO 4217 code for valueFormat currency, printed with the narrow symbol ("$128,450").
decimalsnumberFraction digits in the tooltip, labels and table; auto by magnitude when unset. Ticks stay short.
formatValue(value: number, context: { series: string; kind: "tooltip" | "tick" | "label" | "table" }) => stringReplaces the built-in value formatting everywhere.
formatX(value: unknown, context: "axis" | "tooltip" | "table") => stringReplaces the category label formatting (short on the axis, long in the tooltip and table).
localedefault""stringLocale for numbers and dates. Empty uses the UIProvider / LocaleProvider locale.
yDomaindefault"["auto", "auto"]"BarChartDomain = [number | "auto", number | "auto"]Fixed value bounds; zero is always included. Ignored by percent (always 0 to 100%).
showGriddefaulttruebooleanHairlines at the value ticks (horizontal lines, or vertical ones for horizontal bars).
showXAxisdefaulttruebooleanThe labels under the plot: categories (thinned so they never collide), or value ticks for horizontal bars.
showYAxisdefaulttruebooleanThe labels beside the plot: compact value ticks ("$128K"), or the category column for horizontal bars.
yAxisSidedefault"start""start" | "end"end puts the value ticks on the right, finance style (vertical bars; horizontal bars keep categories on the left).
showLegenddefaulttruebooleanThe legend; it only renders with two or more series (one series is named by the card title).
legendPositiondefault"top""top" | "bottom"Legend above or below the plot.
legendToggledefaulttruebooleanLegend entries are buttons that hide and show their series. The last visible series cannot be hidden.
showTooltipdefaulttruebooleanThe floating readout. When off, the band, keyboard and screen reader announcements still work.
tooltipModedefault"auto""auto" | "float" | "fixed"float rides beside the inspected bars, fixed pins above the plot (never under a finger); auto is fixed on touch screens.
referenceLines{ value: number; label?: string; color?: string }[]Dashed target or average lines with a label ("Target $120,000"); always inside the value range.
animateddefaulttruebooleanBars grow on first view and glide on data, type and legend changes. Reduced motion always shows the final state.
loadingdefaultfalsebooleanA bar-shaped skeleton in the plot's box.
emptyStateReactNodeReplaces the built-in empty state (shown when data is empty or every value is null).
labeldefault""stringAccessible name of the figure and caption of its data table. Empty joins the series names.
descriptiondefault""stringA one-sentence takeaway for screen readers ("September brought the highest collections of the year").
surfacedefault""stringThe color behind the chart, used for label halos. Empty uses the surface token; set it when the chart sits on a card.
hiddenSeriesstring[]Hidden series keys when you control legend toggling; pair with onHiddenSeriesChange.
defaultHiddenSeriesstring[]Series keys hidden at first, when the legend toggles on its own.
onHiddenSeriesChange(keys: string[]) => voidCalled when a legend entry hides or shows a series.
activeIndexnumber | nullThe inspected category index when you control it, so two charts can share one highlight.
defaultActiveIndexnumber | nullThe category inspected at first (a pinned readout), uncontrolled.
onActiveIndexChange(index: number | null) => voidCalled as the highlighted category changes (mouse, touch or keyboard) and with null when it clears.
onDatumClick(event: { index: number; datum: ChartDatum; series?: string }) => voidA click or tap on a category (series is the bar under the pointer, else the first visible series) or Enter on the inspected one.
labelsPartial<BarChartLabels>Strings: empty, loading, table, total, inspectHint, inspectHintVertical, showSeries, hideSeries, category (Category, the table's first column).
classNamesPartial<Record<BarChartSlot, string>>Classes for legend, plot, tooltip, table, grid, xAxis, yAxis, bar, band, value, category, referenceLine and inspect.
Parts
DEFAULT_BAR_CHART_LABELS
constantThe English strings the chart uses (empty and loading text, the data table and its Category column, the keyboard hints for both orientations, show and hide series). Spread it and override what you translate.
BarChartLabels
typeThe shape of labels: the chart kit's shared strings plus category and inspectHintVertical.
BarChartSeries
typeOne bar key: { key, label?, color?, name? }, in color and stack order.
Examples
Negative values
Cash-flow variation: bars grow down from zero, getBarColor paints them by sign.
Horizontal, long names
Categories in a label column with compact value labels at the tips.
Stacked with totals
Invoices by status per month, the total above each stack.
Current period
highlightLast on 12 weeks of orders in the neutral ink.
100% stacked
Channel shares per quarter with the share inside each segment.
Loading and empty
The skeleton and the empty state in the plot's box.
Phone, 360px
Horizontal bars with the pinned touch readout.
Dependencies
Files
- components/ui/bar-chart.tsx
FAQ
Is the React Bar chart component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Bar chart?
Run npx wingo-ui@latest add bar-chart. It also adds the Wingo UI items it builds on (Chart kit, Motion presets, UIProvider and useLocale) and the npm package motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/bar-chart.
How does Bar chart behave on phones?
On a phone, tap a column to pin the readout above the plot, drag sideways to scrub (a vertical drag still scrolls the page), tap it again or anywhere outside to clear it.
See also
Used in
More React chart and stat components
Articles using this component
- React Funnel Chart: Conversion, Drop-off and Cohorts
Component guides9 min read
- React Charts Tailwind: 4 Charts and a Kit for Any Theme
Component guides10 min read