Bar Chart
Composable bar charts with grouped series, dual axes, annotations, and animated legend toggles.
Build bar charts from composable primitives. Combine BarMark with lines, areas, axes, cursors, tooltips, and legends in any configuration.
Basic bar chart
The simplest bar chart needs a Chart wrapper, one BarMark, and axes. Pass radius for rounded corners.
import { BarMark, Chart, Cursor, Grid, Tooltip, XAxis, YAxis } from "@exhibit/charts";
const data = [
{ month: "Jan", sales: 4200, target: 4000 },
{ month: "Feb", sales: 4800, target: 4500 },
// ...
];
<Chart data={data} xDataKey="month" height={300}>
<Grid horizontal />
<XAxis />
<YAxis tickFormat={(v) => `$${(v / 1000).toFixed(0)}k`} />
<BarMark dataKey="sales" name="Sales" color="var(--color-chart-1)" radius={4} />
<Cursor type="band" />
<Tooltip />
</Chart>Grouped bars
Add multiple BarMark components for grouped bars. Each bar series gets its own colour and is automatically positioned side-by-side within each category. Define your series as a config array and map over it for cleaner code.
const series = [
{ dataKey: "productA", name: "Product A", color: "var(--color-chart-1)" },
{ dataKey: "productB", name: "Product B", color: "var(--color-chart-2)" },
{ dataKey: "productC", name: "Product C", color: "var(--color-chart-3)" },
{ dataKey: "productD", name: "Product D", color: "var(--color-chart-4)" },
];
<Chart data={data} xDataKey="quarter" height={350}>
<Grid horizontal />
<XAxis />
<YAxis />
{series.map((s) => (
<BarMark key={s.dataKey} {...s} radius={3} />
))}
<Cursor highlight type="band" />
<Tooltip />
<Legend />
</Chart>Bar styling
Control bar appearance with radius for rounded corners, and activeOpacity / inactiveOpacity for hover feedback.
<BarMark
dataKey="sales"
name="Sales"
color="var(--color-chart-1)"
radius={6}
activeOpacity={1}
inactiveOpacity={0.2}
/>| Prop | Type | Default | Description |
|---|---|---|---|
radius | number | 0 | Corner radius in pixels |
activeOpacity | number | 1 | Opacity of bars in the hovered group (requires <Cursor highlight>) |
inactiveOpacity | number | 0.3 | Opacity of non-hovered bars |
Cursor modes
The Cursor component supports both band and line types. Combine with highlight to dim non-hovered groups.
Band cursor with highlight
Line cursor with highlight
// Band cursor — shaded region over the active category
<Cursor type="band" highlight />
// Line cursor — thin vertical line
<Cursor type="line" highlight />| Prop | Behaviour |
|---|---|
type="band" | Shaded rectangle spanning the full category width. Best for bar charts with categorical X axes. |
type="line" | Dashed vertical line at the active data point. Works with both band and linear scales. |
highlight | When true, dims all non-hovered bar groups and non-closest lines to draw focus to the active data. |
Mixed bar and line
Combine BarMark and LineMark in the same chart. Use dual Y axes when the bar and line series have different units or scales.
import {
BarMark, Chart, Cursor, Grid, Legend, LineMark, Tooltip, XAxis, YAxis
} from "@exhibit/charts";
<Chart data={data} xDataKey="month" height={400} padding={{ left: 56, right: 56 }}>
<Grid horizontal />
<XAxis />
<YAxis label="Precipitation" tickFormat={(v) => `${v} ml`} />
<YAxis position="right" label="Temperature" tickFormat={(v) => `${v} °C`} />
<BarMark dataKey="evaporation" name="Evaporation" color="var(--color-chart-1)" />
<BarMark dataKey="precipitation" name="Precipitation" color="var(--color-chart-2)" />
<BarMark dataKey="runoff" name="Runoff" color="var(--color-chart-4)" />
<LineMark
dataKey="temperature"
name="Temperature"
yAxisId="right"
color="var(--color-chart-3)"
markerPosition="snap"
/>
<Cursor highlight type="band" />
<Tooltip />
<Legend />
</Chart>Dual axes with bar + line overlay
Assign any mark to a second Y axis via yAxisId="right". This works for bars, lines, and areas.
<Chart data={data} xDataKey="month" height={380} padding={{ left: 56, right: 56 }}>
<Grid horizontal />
<XAxis />
<YAxis label="kWh" />
<YAxis position="right" label="Price" domain={[0, 0.4]} tickFormat={(v) => `$${v.toFixed(2)}`} />
<BarMark dataKey="consumption" name="Consumption" color="var(--color-chart-1)" radius={2} />
<BarMark dataKey="generation" name="Generation" color="var(--color-chart-2)" radius={2} />
<LineMark
dataKey="price"
name="Price/kWh"
yAxisId="right"
color="#f59e0b"
curve="monotoneX"
dot
markerPosition="snap"
strokeWidth={2.5}
/>
<Cursor highlight type="band" />
<Tooltip>{/* custom tooltip */}</Tooltip>
<Legend />
</Chart>Area + bar combination
Layer AreaMark and BarMark in the same chart. The area renders behind the bars, creating a contextual backdrop for the bar data.
import { AreaMark, BarMark, Chart, Cursor, Grid, Legend, Tooltip, XAxis, YAxis } from "@exhibit/charts";
<Chart data={data} xDataKey="hour" height={350} padding={{ left: 56, right: 56 }}>
<Grid horizontal />
<XAxis />
<YAxis label="Vehicles" />
<YAxis position="right" label="Incidents" domain={[0, 12]} />
<AreaMark dataKey="volume" name="Traffic volume" color="var(--color-chart-1)" fillOpacity={0.12} />
<BarMark dataKey="incidents" name="Incidents" yAxisId="right" color="#ef4444" radius={3} />
<Cursor highlight type="band" />
<Tooltip>{/* custom tooltip */}</Tooltip>
<Legend />
</Chart>Custom tooltips
Pass a render function to <Tooltip> for full control. Combine TooltipShell, TooltipHeader, and TooltipRow with your own markup for computed values, badges, or conditional formatting.
<Tooltip>
{({ datum }) => {
const sales = typeof datum.sales === "number" ? datum.sales : 0;
const target = typeof datum.target === "number" ? datum.target : 0;
const delta = sales - target;
const pct = target > 0 ? ((delta / target) * 100).toFixed(1) : "0";
return (
<TooltipShell className="min-w-44">
<TooltipHeader>{String(datum.month)}</TooltipHeader>
<TooltipRow color="var(--color-chart-1)" indicator="square" label="Sales" value={`$${sales.toLocaleString()}`} />
<TooltipRow color="var(--color-chart-4)" indicator="dot" label="Target" value={`$${target.toLocaleString()}`} />
<div className="border-border mt-1 border-t pt-1">
<span className={`text-xs font-medium ${delta >= 0 ? "text-green-600" : "text-red-600"}`}>
{delta >= 0 ? "+" : ""}{pct}% vs target
</span>
</div>
</TooltipShell>
);
}}
</Tooltip>Annotations and reference lines
Use ReferenceLine for horizontal or vertical reference markers and Annotation for point markers with labels. These compose naturally with bar charts to highlight averages, thresholds, and extremes.
import { Annotation, BarMark, Chart, ReferenceLine, ... } from "@exhibit/charts";
<Chart data={data} xDataKey="month" height={420} padding={{ left: 48, right: 48, top: 24, bottom: 40 }}>
<Grid horizontal />
<XAxis />
<YAxis />
<BarMark dataKey="rainfall" name="Rainfall" color="#5470c6" radius={2} />
<BarMark dataKey="evaporation" name="Evaporation" color="#91cc75" radius={2} />
{/* Average reference lines */}
<ReferenceLine y={41.5} color="#5470c6" label="Avg: 41.5" strokeDasharray="6 4" />
<ReferenceLine y={48.0} color="#91cc75" label="Avg: 48.0" labelPosition="start" strokeDasharray="6 4" />
{/* Max/min annotations aligned to specific bar series */}
<Annotation x="Aug" y={162.2} barAlign="rainfall" color="#5470c6" label="Max: 162.2" />
<Annotation x="Jan" y={2.0} barAlign="rainfall" color="#5470c6" label="Min: 2.0" labelPosition="bottom" />
<Cursor type="band" />
<Tooltip />
<Legend />
</Chart>Props
Chart
| Prop | Type | Default | Description |
|---|---|---|---|
data | T[] | required | Array of data objects |
xDataKey | string & keyof T | required | Key used for the X axis |
height | number | 400 | Chart height in pixels |
padding | Partial<Padding> | { top: 20, right: 20, bottom: 40, left: 48 } | Padding around the plot area |
animate | boolean | true | Enable entrance animations |
className | string | -- | Additional CSS class |
BarMark
| Prop | Type | Default | Description |
|---|---|---|---|
dataKey | string | required | Data key for this bar series |
name | string | -- | Display name (used in legend and tooltip) |
color | string | var(--color-chart-1) | Bar fill colour |
yAxisId | "left" | "right" | "left" | Which Y axis to bind to |
radius | number | 0 | Bar corner radius |
activeOpacity | number | 1 | Opacity of hovered bars (when highlight is on) |
inactiveOpacity | number | 0.3 | Opacity of non-hovered bars |
ReferenceLine
| Prop | Type | Default | Description |
|---|---|---|---|
y | number | -- | Horizontal line at this Y value |
x | number | string | -- | Vertical line at this X value |
color | string | currentColor | Line and label colour |
label | string | -- | Text label |
labelPosition | "start" | "end" | "end" | Where to place the label |
strokeDasharray | string | "6 4" | Dash pattern (set to "" for solid) |
strokeOpacity | number | 0.6 | Line opacity |
strokeWidth | number | 1 | Line thickness |
yAxisId | "left" | "right" | "left" | Which Y axis to reference |
Annotation
| Prop | Type | Default | Description |
|---|---|---|---|
x | number | string | required | X position (category name or numeric value) |
y | number | required | Y value |
barAlign | string | -- | Align marker to a specific bar in a grouped chart |
color | string | currentColor | Marker and label colour |
label | string | -- | Label text |
labelPosition | "top" | "bottom" | "top" | Show label above or below the marker |
size | number | 5 | Marker radius |
yAxisId | "left" | "right" | "left" | Which Y axis to reference |
Cursor
| Prop | Type | Default | Description |
|---|---|---|---|
axis | "x" | "y" | "x" | Which axis the cursor aligns to |
label | boolean | (value) => ReactNode | -- | Display the active axis value |
marker | boolean | { className?: string; behaviour?: "interpolate" | "snap" | "absolute" } | -- | Render a dot at the cursor/series intersection |
type | "line" | "band" | "line" | Cursor style |
highlight | boolean | false | Dim non-hovered bars and non-closest lines |
lineColor | string | -- | Line cursor colour |
lineWidth | number | 1.5 | Line cursor width |
lineDash | string | "4 4" | Line cursor dash pattern |
bandColor | string | -- | Band cursor fill colour |
bandOpacity | number | 0.06 | Band cursor fill opacity |
Tooltip
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | (props: TooltipRenderProps) => ReactNode | -- | Custom content (auto-generated if omitted) |
offset | { x?: number; y?: number } | { x: 16, y: -8 } | Offset from cursor |
stiffness | number | 0.18 | Spring stiffness for follow animation (0-1) |
Legend
| Prop | Type | Default | Description |
|---|---|---|---|
items | LegendItem[] | -- | Explicit items (auto-generated from marks if omitted) |
position | "top" | "bottom" | "bottom" | Position relative to the chart |
className | string | -- | Additional CSS class |
XAxis / YAxis
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | -- | Axis label |
tickFormat | (value) => string | -- | Custom tick label formatter |
tickCount | number | auto | Number of ticks |
position | "left" | "right" | "left" | Y axis position |
domain | [number | "auto", number | "auto"] | auto | Y axis domain |
hideAxisLine | boolean | false | Hide the axis line |
Grid
| Prop | Type | Default | Description |
|---|---|---|---|
horizontal | boolean | true | Show horizontal grid lines |
vertical | boolean | false | Show vertical grid lines |
color | string | -- | Grid line colour |
strokeDasharray | string | "3 3" | Dash pattern |