BarChart
A bar chart supporting vertical/horizontal layouts, grouped/stacked/percent modes, presets, comparison bars, target bars, and sorting.
import { BarChart } from "metricui";Use BarChart for categorical comparison — comparing values across discrete categories like months, channels, or products. Supports vertical and horizontal layouts, grouped/stacked/percent modes, comparison overlays, reference lines, and threshold bands. For time-series trends, use AreaChart or LineChart instead.
Basic Example
<BarChart
data={[
{ month: "Jan", revenue: 42000 },
{ month: "Feb", revenue: 45200 },
{ month: "Mar", revenue: 48100 },
{ month: "Apr", revenue: 51800 },
{ month: "May", revenue: 49200 },
{ month: "Jun", revenue: 55400 },
]}
keys={["revenue"]}
indexBy="month"
title="Monthly Revenue"
format={{ style: "currency" }}
/>Grouped & Stacked
When you have multiple keys, use groupMode to control how bars are arranged. Use preset for quick configuration, or set layout and groupMode individually.
<BarChart
preset="grouped"
data={multiKeyData}
keys={["visitors", "conversions"]}
indexBy="channel"
title="Visitors vs Conversions"
format={{ style: "number" }}
/><BarChart
preset="percent"
data={threeKeyData}
keys={["organic", "paid", "referral"]}
indexBy="quarter"
title="Channel Mix"
/>Horizontal Layout
Horizontal bars are ideal for long category labels. The left margin auto-adjusts to fit the longest label. Use the sort prop to rank bars by value.
<BarChart
preset="horizontal"
data={horizontalData}
keys={["signups"]}
indexBy="category"
title="Signups by Plan"
sort="desc"
format={{ style: "number" }}
/>Comparison Overlay
Pass comparisonData to render a dashed outline showing a previous period alongside current bars. The comparison data must have the same shape and index values as the primary data.
<BarChart
data={currentData}
keys={["revenue"]}
indexBy="month"
comparisonData={previousPeriodData}
title="Revenue: Current vs Previous"
format={{ style: "currency" }}
/>Reference Lines & Thresholds
Add referenceLines for targets or benchmarks, and thresholds for colored value-axis bands (e.g. danger/warning zones).
<BarChart
data={revenueData}
keys={["revenue"]}
indexBy="month"
title="Revenue vs Target"
format={{ style: "currency" }}
referenceLines={[{
axis: "y",
value: 50000,
label: "Target",
color: "#EF4444",
style: "dashed",
}]}
thresholds={[{
from: 40000,
to: 50000,
color: "#F59E0B",
label: "Warning zone",
}]}
/>Data States
Every component handles loading, empty, and error states. Pass individual props or use the grouped state prop.
Loading
Error
Failed to load data
// Loading state
<BarChart data={[]} keys={["revenue"]} indexBy="month" loading />
// Error state
<BarChart data={[]} keys={["revenue"]} indexBy="month" error={{ message: "Failed to load" }} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
preset | BarChartPreset | — | One-prop configuration shortcut. "default" = vertical stacked, "grouped" = side-by-side bars, "stacked" = stacked bars, "percent" = 100% normalized stack, "horizontal" = horizontal bars, "horizontal-grouped" = horizontal side-by-side. Individual props override preset values. |
data* | Record<string, string | number>[] | — | Array of data rows. Each row is an object with the indexBy field and numeric keys. |
index | string | — | Column key for the x-axis labels. Used with the unified flat-row data format. If omitted with categories, auto-inferred as the first string column. |
categories | Category[] | — | Columns to plot as series. Accepts plain strings or CategoryConfig objects ({ key, label?, format?, color?, axis? }). If omitted with index, auto-inferred as all number columns. |
keys* | string[] | — | Keys (series names) to render as bars. |
indexBy* | string | — | Field name used as the category axis. |
comparisonData | Record<string, string | number>[] | — | Previous period data rendered as dashed outline bars behind the actual bars. Same row shape as `data`. Shows period-over-period comparison at a glance. |
title | string | — | Chart card title. |
subtitle | string | — | Chart card subtitle. |
description | string | React.ReactNode | — | Description popover content. |
footnote | string | — | Footnote. |
action | React.ReactNode | — | Action slot. |
format | FormatOption | — | Format for value-axis labels and tooltips. |
height | number | 300 | Chart height in px. |
layout | "vertical" | "horizontal" | "vertical" | Bar layout direction. |
groupMode | "stacked" | "grouped" | "percent" | "stacked" | How multiple keys are displayed. "percent" normalizes to 100%. |
padding | number | 0.3 | Gap between bar groups (0-1). |
innerPadding | number | — | Gap between bars in a group. Default: 4 for grouped, -1 for stacked (overlap to eliminate anti-aliasing gaps). |
borderRadius | number | 4 | Corner radius on bars. Set to 0 for stacked modes. |
enableLabels | boolean | false | Show formatted value labels on bars. |
labelPosition | "inside" | "outside" | "auto" | "auto" | Where labels appear. |
sort | "none" | "asc" | "desc" | "none" | Sort bars by total value. 'desc' puts highest bars first — great for leaderboards and ranked comparisons. |
enableNegative | boolean | false | Enable diverging colors for negative values. Positive bars use series color, negative bars use negativeColor. Use for P&L, variance, or NPS charts. |
negativeColor | string | "#EF4444" | Color for negative-value bars when enableNegative is true. |
targetData | Record<string, number>[] | — | Target/goal values rendered as semi-transparent ghost bars behind actual bars. Same shape as `data`. Use for actual-vs-target comparisons (e.g., sales targets, quotas). |
targetColor | string | — | Color for target bars. Default: theme-aware muted color. |
seriesStyles | Record<string, BarSeriesStyle> | — | Per-key color overrides. BarSeriesStyle has { color?: string }. |
colors | string[] | — | Series colors. Default: theme series palette. |
referenceLines | ReferenceLine[] | — | Horizontal or vertical reference lines for targets, averages, or benchmarks. Each: `{ axis: 'x'|'y', value, label?, color?, style? }`. |
thresholds | ThresholdBand[] | — | Colored range bands for danger/warning/safe zones. Each: `{ from, to, color, opacity?, label? }`. Rendered behind bars. |
legend | boolean | LegendConfig | — | Legend with series toggle. Cmd/Ctrl+click to solo. Default: shown for multi-key data. |
xAxisLabel | string | — | Label along the X-axis (category axis in vertical mode, value axis in horizontal). Auto-hidden at narrow widths. |
yAxisLabel | string | — | Label along the Y-axis (value axis in vertical mode, category axis in horizontal). Auto-hidden at narrow widths. |
onBarClick | (bar: { id: string | number; value: number | null; label: string; key: string; indexValue: string | number }) => void | — | Click handler for bars. Use for drill-down navigation — e.g., click a bar to navigate to detail view for that category. |
dense | boolean | false | Compact layout. |
chartNullMode | ChartNullMode | "gap" | Null handling. Only "zero" transforms bar data. |
animate | boolean | true | Enable/disable animation. |
variant | CardVariant | — | Card variant (supports custom strings). CSS-variable-driven via [data-variant]. |
className | string | — | Additional CSS class names. |
classNames | { root?: string; header?: string; chart?: string; legend?: string } | — | Sub-element class name overrides. |
id | string | — | HTML id attribute. |
data-testid | string | — | Test id. |
loading | boolean | — | Loading state. |
empty | EmptyState | — | Empty state. |
error | ErrorState | — | Error state. |
stale | StaleState | — | Stale data indicator. |
crossFilter | boolean | { field: string } | — | Enable cross-filter signal on click. true uses the indexBy field, { field } overrides. Emits selection via CrossFilterProvider — does NOT change chart appearance. Dev reads selection with useCrossFilter() and filters their own data. |
tooltipHint | boolean | string | — | Show action hint at bottom of tooltip. true: auto ('Click to drill down' / 'Click to filter'). string: custom hint text. false: disable. Default: respects MetricConfig.tooltipHint (true). |
drillDown | boolean | ((event: { indexValue: string; data: Record<string, unknown> }) => React.ReactNode) | — | Enable drill-down on click. `true` auto-generates a summary KPI row + filtered DataTable from the chart's source data. Pass a render function for full control over the panel content. Requires DrillDown.Root wrapper. When both drillDown and crossFilter are set, drillDown wins. |
drillDownMode | DrillDownMode | "slide-over" | Presentation mode for the drill-down panel. "slide-over" (default) slides from the right, full height. "modal" renders centered and compact. |
Data Shape
// Each row has an index field + numeric fields for each key
type BarData = Record<string, string | number>[];
// Example:
const data = [
{ month: "Jan", revenue: 4000, expenses: 2400 },
{ month: "Feb", revenue: 4500, expenses: 2100 },
];
const keys = ["revenue", "expenses"];
const indexBy = "month";Notes
- Uses forwardRef.
- Uses forwardRef — attach a ref to the root div.
- Presets set sensible defaults; individual props override preset values.
- innerPadding defaults to -1 for stacked mode to eliminate SVG anti-aliasing gaps between segments.
- borderRadius is automatically set to 0 for stacked and percent modes.
- Horizontal layout auto-computes left margin from longest category label.
- The HoverDimLayer dims non-hovered bar groups for visual focus.
- crossFilter prop emits a selection signal on click — it does NOT change the chart's appearance. The dev reads the selection via useCrossFilter() and filters their own data.
- drillDown={true} auto-generates a summary KPI row + filtered DataTable from the chart's source data. Pass a render function for custom panel content. Requires DrillDown.Root wrapper.
- When both drillDown and crossFilter are set on the same component, drillDown wins.
Playground
Experiment with every prop interactively. Adjust the controls on the right to see the component update in real time.
Live Preview
2025 fiscal year
Code
<BarChart
data={[{ month: "Jan", revenue: 42000 }, ...]}
keys={["revenue"]}
indexBy="month"
title="Monthly Revenue"
subtitle="2025 fiscal year"
format={{ style: "currency" }}
/>Props
Adjust props to see the chart update in real time
Sets layout + groupMode defaults. Individual props still override.
Switch between sample datasets
Applied to value-axis labels and tooltips
Bar orientation
How multiple keys are displayed
Gap between bar groups (0-0.9)
Gap between bars in a group
Corner radius on bars
Sort bars by total value