M
MetricUI
Charts

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

Monthly Revenue
<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.

Visitors vs Conversions
<BarChart
  preset="grouped"
  data={multiKeyData}
  keys={["visitors", "conversions"]}
  indexBy="channel"
  title="Visitors vs Conversions"
  format={{ style: "number" }}
/>
Channel Mix
<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.

Signups by Plan
<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.

Revenue: Current vs Previous
<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).

Revenue vs Target
<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

PropTypeDescription
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

Chart height in px.

layout
"vertical" | "horizontal"

Bar layout direction.

groupMode
"stacked" | "grouped" | "percent"

How multiple keys are displayed. "percent" normalizes to 100%.

padding
number

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

Corner radius on bars. Set to 0 for stacked modes.

enableLabels
boolean

Show formatted value labels on bars.

labelPosition
"inside" | "outside" | "auto"

Where labels appear.

sort
"none" | "asc" | "desc"

Sort bars by total value. 'desc' puts highest bars first — great for leaderboards and ranked comparisons.

enableNegative
boolean

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

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

Compact layout.

chartNullMode
ChartNullMode

Null handling. Only "zero" transforms bar data.

animate
boolean

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

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

Monthly Revenue

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