BulletChart
Bullet chart for comparing actual values against targets with qualitative range bands.
import { BulletChart } from "metricui";Use BulletChart to compare actual performance against targets with qualitative range bands. Ideal for OKR scorecards, KPI targets, and quota tracking. Supports full BulletDatum format and a simpleData shorthand, horizontal and vertical layouts, configurable range colors, and theme-aware styling. For single-value progress, use Gauge or KpiCard instead.
Basic Example
Pass an array of bullet data with ranges, measures, and markers to render a bullet chart.
<BulletChart
data={[
{
id: "Revenue",
ranges: [50000, 80000, 100000],
measures: [72000],
markers: [85000],
},
]}
title="Revenue vs Target"
format="currency"
height={120}
/>Simple Data Format
Use simpleDatafor the common "value vs target" case. Ranges are auto-generated from zone percentages (default: [60, 80, 100] of max).
<BulletChart
simpleData={[
{ label: "Revenue", value: 72000, target: 85000 },
{ label: "Users", value: 680, target: 750 },
{ label: "NPS", value: 74, target: 80, max: 100 },
]}
title="Team Scorecard"
/>Multiple Metrics
Pass multiple items in the data array to compare several KPIs side by side. Height auto-calculates from item count.
<BulletChart
data={[
{ id: "Revenue", ranges: [50000, 80000, 100000], measures: [72000], markers: [85000] },
{ id: "Users", ranges: [500, 800, 1000], measures: [680], markers: [750] },
{ id: "NPS", ranges: [30, 60, 100], measures: [74], markers: [80] },
]}
title="Q4 Performance"
/>Vertical Layout
Set layout="vertical" for a column-oriented bullet chart. Useful when vertical space is plentiful and you want to read values top-to-bottom.
<BulletChart
data={[
{ id: "Revenue", ranges: [50000, 80000, 100000], measures: [72000], markers: [85000] },
{ id: "Users", ranges: [500, 800, 1000], measures: [680], markers: [750] },
]}
layout="vertical"
title="Vertical Bullets"
height={300}
/>Custom Styling
Customize measureSize, markerSize, spacing, and titlePosition to tune the visual density and layout.
Actuals vs targets
<BulletChart
simpleData={[
{ label: "MRR", value: 85000, target: 100000, max: 120000 },
{ label: "NPS Score", value: 72, target: 80, max: 100 },
{ label: "Response Time", value: 120, target: 100, max: 200 },
]}
title="SaaS Metrics"
subtitle="Actuals vs targets"
measureSize={0.3}
markerSize={0.8}
spacing={32}
titlePosition="after"
/>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
<BulletChart data={[]} title="Revenue" loading />
// Error state
<BulletChart data={[]} title="Revenue" error={{ message: "Failed to load" }} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | BulletDatum[] | — | Full bullet data with id, title, ranges, measures, markers. |
simpleData | SimpleBulletData[] | — | Simple shorthand format: label, value, target, max, zones. data takes precedence. |
title | string | — | Chart card title. |
subtitle | string | — | Chart card subtitle. |
description | string | React.ReactNode | — | Description popover. |
footnote | string | — | Footnote. |
action | React.ReactNode | — | Action slot. |
format | FormatOption | — | Format for values in tooltips. |
height | number | — | Height in px. Default: auto-calculated from item count. |
layout | "horizontal" | "vertical" | "horizontal" | Layout direction. |
spacing | number | 40 | Gap between bullet items in px. |
rangeColors | string[] | — | Range color scheme. Default: theme-aware greens. |
measureColors | string[] | — | Measure (bar) color scheme. Default: theme accent. |
markerColors | string[] | — | Marker color scheme. Default: theme foreground. |
measureSize | number | 0.4 | Size of the measure bar relative to the range (0-1). |
markerSize | number | 0.6 | Size of the marker relative to the range height. |
titlePosition | "before" | "after" | "before" | Title position relative to the bullet. |
showAxis | boolean | true | Show axis. |
animate | boolean | true | Enable/disable animation. |
variant | CardVariant | — | Card variant. |
dense | boolean | — | Compact layout. |
className | string | — | Additional CSS class names. |
classNames | { root?: string; header?: string; chart?: 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 indicator. |
Data Shape
interface BulletDatum {
id: string;
title?: React.ReactNode;
ranges: number[]; // Cumulative endpoints: [150, 225, 300]
measures: number[]; // Actual value bars
markers?: number[]; // Target marker lines
}
interface SimpleBulletData {
label: string;
value: number;
target?: number;
max?: number; // Default: auto from target or value * 1.2
zones?: number[]; // Percentages of max. Default: [60, 80, 100]
}Notes
- Uses forwardRef.
- Uses forwardRef — attach a ref to the root div.
- simpleData auto-generates ranges from zone percentages (default: [60, 80, 100]). data takes precedence when non-empty.
- Height auto-calculates from item count when not specified.
- Range colors are theme-aware (light green shades in light mode, dark green shades in dark mode).
- Marker lines represent targets; measures are the actual value bars.
- The tooltip uses ChartTooltip and respects the format prop.