Bullet Chart
import { BulletChart } from '@absoluteui/components/bullet-chart';import '@absoluteui/components/bullet-chart/style.css';Using Angular? See Bullet Chart for Angular.
What it does
Section titled “What it does”An accessible SVG bullet chart: compact horizontal measures against a target and qualitative ranges (for example poor / satisfactory / good). It provides:
- one row per measure on a shared value axis: up to three shaded range bands, a measure bar growing from zero, and a target tick;
- ranges in three neutral shades (not palette colors), named in each row’s accessible label, tooltip, and table (
rangeLabels), so meaning never depends on shading; - keyboard-reachable measure bars (one tab stop, Up/Down between rows) whose names read value, target, and range, with a tooltip on focus and hover;
- a generated summary (every measure, and how many are at or above target) and a data table fallback (Measure, Value, Target, Range).
No animation.
When to use
Section titled “When to use”- Key performance indicators against targets in a dashboard, with a qualitative scale.
- Replacing gauges and meters with a compact, comparable row layout.
When not to use
Section titled “When not to use”- Measures on very different scales (all rows share one value axis): use one chart per scale or Stat Card.
- Trends over time: use Line Chart or Sparkline.
- A single value against a maximum without target or ranges: use Progress Bar or Gauge.
Examples
Section titled “Examples”Measures against targets with named ranges
Section titled “Measures against targets with named ranges”import { BulletChart } from '@absoluteui/components/bullet-chart';
const host = document.createElement('div');document.body.append(host);BulletChart.create(host, { label: 'Quarterly goals', measures: [ { id: 'revenue', label: 'Revenue', value: 270, target: 250, ranges: [150, 225, 300] }, { id: 'profit', label: 'Profit', value: 22, target: 26, ranges: [20, 25, 30] }, { id: 'orders', label: 'New orders', value: null, target: 180, ranges: [100, 150, 200] }, ], rangeLabels: ['Poor', 'Satisfactory', 'Good'], height: 180,});Percent values on a fixed domain, live update
Section titled “Percent values on a fixed domain, live update”import { BulletChart, type BulletMeasure } from '@absoluteui/components/bullet-chart';
const host = document.querySelector<HTMLElement>('#sla');if (host) { const measures: BulletMeasure[] = [ { id: 'uptime', label: 'Uptime', value: 0.994, target: 0.999, ranges: [0.99, 0.995, 1] }, { id: 'first-response', label: 'First response in 1 h', value: 0.87, target: 0.9, ranges: [0.8, 0.9, 1] }, ]; const chart = BulletChart.create(host, { label: 'Service levels', measures, rangeLabels: ['Breach', 'At risk', 'Met'], valueFormat: { style: 'percent', maximumFractionDigits: 1 }, valueAxis: { min: 0.75, max: 1 }, color: 2, }); chart.update({ measures: measures.map((m) => (m.id === 'uptime' ? { ...m, value: 0.9991 } : m)) });}import { BulletChart } from '@absoluteui/components/bullet-chart';import '@absoluteui/components/bullet-chart/style.css';
const host = document.querySelector<HTMLElement>('#kpis');if (host) { BulletChart.create(host, { label: 'Customer satisfaction', measures: [ { id: 'csat', label: 'CSAT', value: 4.3, target: 4.5, ranges: [3, 4, 5] }, { id: 'nps', label: 'NPS / 20', value: 2.4, target: 2.5, ranges: [1, 2, 5] }, ], rangeLabels: ['Poor', 'Fair', 'Good'], valueAxis: { min: 0, max: 5 }, height: 160, });}More usage
Section titled “More usage”import { BulletChart } from '@absoluteui/components/bullet-chart';
const host = document.createElement('div');document.body.append(host);BulletChart.create(host, { label: 'Quarterly goals', measures: [ { id: 'rev', label: 'Revenue', value: 270, target: 250, ranges: [150, 225, 300] }, { id: 'profit', label: 'Profit', value: 22, target: 26, ranges: [20, 25, 30] }, ], rangeLabels: ['Poor', 'Satisfactory', 'Good'],});Anatomy
Section titled “Anatomy”<div class="aui-chart aui-bullet-chart [aui-chart--patterns]"> <div class="aui-chart__header">title, description</div> <ul class="aui-chart__legend" hidden/> <div class="aui-chart__body"> <svg class="aui-chart__svg" role="group" aria-roledescription="bullet chart" aria-labelledby="T" aria-describedby="S"> <g class="aui-chart__grid" aria-hidden="true"/> <g class="aui-chart__axis aui-chart__axis--value" aria-hidden="true"/> <g class="aui-bullet-chart__rows aui-chart__series--c1"> <g class="aui-bullet-chart__row"> <g class="aui-bullet-chart__ranges" aria-hidden="true"> <rect class="aui-bullet-chart__range aui-bullet-chart__range--1"/> ... --2, --3 </g> <rect class="aui-chart__bar aui-bullet-chart__measure aui-chart__mark" role="img" aria-label="Revenue: 270, target 250, Good"/> <line class="aui-bullet-chart__target" aria-hidden="true"/> </g> </g> <g class="aui-chart__axis aui-chart__axis--category" aria-hidden="true"><text class="aui-bullet-chart__label">Revenue</text></g> </svg> <div class="aui-chart__tooltip" aria-hidden="true" hidden/> </div> <p class="aui-chart__summary" hidden/> <div class="aui-chart__data">Show data table, table</div></div>API reference
Section titled “API reference”Factory
Section titled “Factory”BulletChart.create(host: HTMLElement, options: BulletChartOptions): BulletChartGlobal configuration key: bulletChart (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Chart title: visible text and the accessible name of the plot (aria-labelledby). Must not be blank. Required. | |
hideLabel |
boolean |
false |
Visually hide the title; it stays the accessible name. |
description |
string |
Visible description below the title, also referenced by aria-describedby (text only). | |
summary |
string |
Replaces the generated accessible summary (measure count, each measure’s name, and how many are at or above target). | |
valueFormat |
Intl.NumberFormatOptions |
Number format for values and targets in names, the tooltip, the data table, the summary, and axis ticks (ticks use fixed fraction digits). | |
formatValue |
(value: number) => string |
Custom value text; overrides valueFormat everywhere except axis ticks. | |
hiddenSeries |
readonly string[] |
Shared chart option; validated but has no effect (a bullet chart has no toggleable series). | |
measures |
readonly BulletMeasure[] |
Rows: { id, label, value: number | null, target?: number, ranges?: number[] }. Ids are unique and non-empty; null, NaN, and infinities are a missing value; target is finite; ranges are up to three strictly ascending finite upper ends (the first range starts at the axis minimum). Required. | |
rangeLabels |
readonly string[] |
Names of the qualitative ranges in the same order as ranges (up to three, for example [“Poor”, “Satisfactory”, “Good”]); read in each measure’s name, tooltip, and table. Default “Range 1”, “Range 2”, “Range 3”. | |
color |
ChartColor (1-8) |
1 |
Palette slot of the measure bars. |
valueAxis |
BulletChartValueAxis ({ label?: string; min?: number; max?: number; tickCount?: number; includeZero?: boolean }) |
Fixed domain (max > min; marks are clamped to it) and approximate tick count (2-20). The automatic domain always includes zero and spans every value, target, and range end. label and includeZero are validated but not drawn or used. | |
legend |
'auto' | 'show' | 'hide' |
"auto" |
Shared chart configuration; a bullet chart never shows a legend (ranges are named in each row’s label). Globally configurable. |
dataTable |
'toggle' | 'visible' | 'hidden' | 'none' |
"toggle" |
Data table fallback (Measure, Value, Target, Range): behind a Show data table button (built on first open), always visible, visually hidden (screen readers only), or none. Globally configurable. |
tooltip |
boolean |
true |
Tooltip on pointer hover and keyboard focus (value, target, range). Globally configurable. |
patterns |
boolean |
false |
Shared chart configuration; toggles the aui-chart–patterns host class. Bullet charts draw no textures (one measure color; ranges are neutral shades named in text). Globally configurable. |
height |
number |
300 |
Plot height in CSS px (integer 120-2000), divided evenly between rows; the width follows the container. Globally configurable. |
locale |
string |
"en-US" |
Number and message locale. Globally configurable. Inherited from common configuration. |
direction |
'ltr' | 'rtl' |
"ltr" |
rtl mirrors the value axis, moves labels to the right, and swaps ArrowLeft/ArrowRight. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
The host element. |
element |
SVGSVGElement |
The plot (svg role=“group”), named by the title and described by the summary. |
summary |
string |
The generated (or custom) accessible summary. |
hiddenSeries |
string[] |
Always empty (a bullet chart has no toggleable series). |
Methods
Section titled “Methods”| Method | Description |
|---|---|
update(options: Partial<BulletChartOptions>): void |
Merge, validate, and re-render. Invalid options throw and keep the previous state. Keyboard focus stays on the same measure (by id). |
setSeriesVisible(id: string, visible: boolean): void |
Shared chart method; re-renders but has no visible effect on a bullet chart. Emits no event. |
destroy(): void |
Remove rendering, host classes, listeners, the resize observer, and the configuration subscription. |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-chart |
Host (shared chart chrome). |
aui-bullet-chart |
Host modifier for this chart type. |
aui-chart--patterns |
patterns option on (no textures are drawn by a bullet chart). |
aui-chart__header / __title / __title--hidden / __description / __toolbar |
Title and description; the toolbar stays hidden. |
aui-chart__legend |
Legend list; always hidden for a bullet chart. |
aui-chart__body / __svg / __layer |
Plot container (height from –aui-chart-height), SVG, and drawing layer. |
aui-chart__grid / __gridline / __zero-line / __axis / __axis--value / __axis--category / __tick-label |
Value axis and the measure label column (aria-hidden). |
aui-chart__series--c1 ... --c8 |
Palette slot class on the rows group (color option); sets –aui-chart-color from color-chart-<n>. |
aui-chart__mark / __mark--active / __mark--hover |
Focusable measure bar and its states. |
aui-chart__bar |
Measure bar paint (series color with a surface-colored separator; text-colored outline on hover and focus). |
aui-chart__tooltip / __tooltip-title / __tooltip-rows / __tooltip-row / __tooltip-label / __tooltip-value / __swatch |
Tooltip (aria-hidden; the focused bar carries the same text). |
aui-chart__empty |
Empty state text (No data). |
aui-chart__summary |
Hidden summary referenced by aria-describedby. |
aui-chart__data / __data--hidden / __table-toggle / __table-scroll / __table / __cell--numeric |
Data table fallback. |
aui-bullet-chart__rows / __row |
All rows, and one group per measure. |
aui-bullet-chart__ranges / __range / __range--1 / __range--2 / __range--3 |
Decorative (aria-hidden) qualitative range bands, darkest first. |
aui-bullet-chart__measure |
The focusable measure bar. |
aui-bullet-chart__target |
Target tick (aria-hidden). |
aui-bullet-chart__label |
Measure label beside the row (aria-hidden). |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-chart-1 | --aui-color-chart-2 | --aui-color-chart-3 | --aui-color-chart-4 | --aui-color-chart-5 | --aui-color-chart-6 | --aui-color-chart-7 | --aui-color-chart-8 | --aui-color-chart-axis | --aui-color-chart-grid | --aui-color-focus-ring | --aui-color-hover | --aui-color-link | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-muted | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-size-xs | --aui-font-weight-medium | --aui-font-weight-regular | --aui-font-weight-semibold | --aui-line-height-normal | --aui-line-height-tight | --aui-radius-md | --aui-radius-sm | --aui-shadow-md | --aui-space-1 | --aui-space-2 | --aui-space-3 | --aui-tooltip-max-width | --aui-touch-target-min
TypeScript definitions
Section titled “TypeScript definitions”type BulletChartConfig = ChartBaseConfig;interface BulletMeasure { id: string; label: string; value: number | null; target?: number; ranges?: readonly number[] }interface BulletChartOptions extends ChartCommonOptions, Partial<BulletChartConfig> { measures: readonly BulletMeasure[]; rangeLabels?: readonly string[]; color?: ChartColor; valueAxis?: BulletChartValueAxis; // { label?; min?; max?; tickCount?; includeZero? }}Accessibility
Section titled “Accessibility”Pattern: SVG bullet chart as a named, described group (role=“group” with aria-roledescription “bullet chart”) of role=“img” measure bars with roving focus (Up/Down between rows); data table fallback (native table: Measure, Value, Target, Range).
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| ArrowDown / ArrowUp | Measure focused | Next / previous row. |
| ArrowRight / ArrowLeft | Measure focused | Also next / previous row (swapped in RTL). |
| Home / End | Measure focused | First / last row. |
| PageUp / PageDown | Measure focused | Ten rows back / forward (clamped to the first / last). |
| Escape | Tooltip visible | Hides the tooltip without moving focus (WCAG 1.4.13); it returns on the next move. |
| Tab / Shift+Tab | Any | One tab stop for the plot (the active measure), then the Show data table button and table region. |
| Element | Role |
|---|---|
| svg.aui-chart__svg | group with aria-roledescription (localized “bullet chart”), named by the title |
| measure bars | img with an aria-label: measure name, formatted value, target, and the named qualitative range |
| data table | table (native) with caption, column headers, and row headers; wide tables scroll in a focusable region |
| range bands, target ticks, measure labels, axis, tooltip | aria-hidden (decorative duplicates of the accessible data) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-labelledby |
svg | id of the visible (or visually hidden) title |
aria-describedby |
svg | description id (when provided) and the generated summary id |
aria-roledescription |
svg | Localized chart type, “bullet chart” |
aria-label |
each measure bar | Localized “{label}: {value}”, then “, target {target}” when a target is set, then “, {range}” when ranges are set (for example “Revenue: 270, target 250, Good”); missing values read “No value” |
tabindex |
active measure bar | 0 on exactly one bar (roving); other bars have none |
aria-expanded / aria-controls |
Show data table button | Table open state and the table region id |
Roving focus: one measure bar is in the tab sequence and remembers the last visited measure (by id), also across re-renders (resize, update). Toggling the table keeps focus on the table button.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Qualitative ranges use three neutral shades (not palette colors) and are named in each row’s accessible label, tooltip, and table (rangeLabels, default “Range 1”…“Range 3”), so meaning never depends on shading.
- The measure falls in the first range whose upper end is at or above the value; values above the last range end read as the last range.
- The generated summary lists the measure count, every measure’s label text, and how many measures with a target are at or above it (for example “1 of 2 at or above target.”).
- The target tick is a 3 px text-colored line across the band; the measure bar is a third of the band height.
- Forced colors: ranges become Canvas with GrayText outlines, targets and labels CanvasText; focus and hover use Highlight.
- No animation: nothing moves on render; hover and focus emphasis transitions are removed under prefers-reduced-motion and animation: none.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver, TalkBack) is pending.
- aria-roledescription support varies; screen readers without it announce “group”.
- Range band boundaries themselves are not exposed; only the range the value falls in is named.
Behavior
Section titled “Behavior”States
Section titled “States”default, empty (“No data”: no measures), measure missing, measure focused (tooltip), row hovered, table open.
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| plot not focused | Tab | active measure focused, tooltip shown |
| measure focused | arrow keys, Home, End, PageUp, PageDown | another measure focused |
| tooltip visible | Escape | tooltip hidden |
| table closed | Show data table | table shown |
| any | update({ measures }) |
re-rendered; focus stays on the same measure id |
Accessibility notes
Section titled “Accessibility notes”- Ranges are neutral shades (22%, 13%, 6% of
color-textovercolor-surface, darkest first) and are always named in text; passrangeLabelsso rows read “Good” rather than “Range 3”. - The value falls in the first range whose upper end is at or above it; values above the last range read as the last range.
- Summary example ends with “1 of 2 at or above target.” (measures with a target and a value).
- Forced colors: ranges
CanvaswithGrayTextoutlines; targets and labelsCanvasText; focus and hoverHighlight. - No animation. Manual screen-reader verification is pending.
Responsive behavior
Section titled “Responsive behavior”Width follows the container; labels take up to 35% of the width and are truncated with an ellipsis. Bands are at most 40 px tall; the measure bar is a third of the band. Reflows at 320 CSS px.
Right-to-left
Section titled “Right-to-left”The value axis is mirrored (bars grow leftward from zero), labels move to the right, ArrowLeft/ArrowRight swap.
Theming
Section titled “Theming”Range shades derive from color-text and color-surface, so they follow every theme. The measure bar uses one palette slot (color, default color-chart-1).
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { bulletChart: { height: 200, dataTable: 'visible' } } });Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
Security
Section titled “Security”All text uses textContent; SVG uses createElementNS; colors are classes; the height and tooltip position use the CSSOM. No innerHTML or style attributes.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry. Values are not logged; validation errors name the option, never its value.
Performance
Section titled “Performance”Budget: create with 30 measures < 30 ms in headless Chromium (e2e performance test), including the first render.
Supported data scale
Section titled “Supported data scale”Up to about 30 measures per chart; raise height with the row count (bands shrink with the row height).
Edge cases
Section titled “Edge cases”- No measures: “No data”.
- Missing value (
null,NaN, infinity): no bar (a zero-width focusable mark), “No value” in its name and table; excluded from the at-or-above-target count. - No target: no tick, “-” in the table. No ranges: no bands, “-” in the Range column.
- Negative values: bars grow from zero toward the negative side; the domain includes zero.
- A fixed
valueAxisdomain clamps bars, targets, and bands to its edges. - Fewer
rangeLabelsthan ranges: the rest read “Range {n}”.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
BULLET_CHART_OPTIONS |
configuration | Blank label; measures not an array; measure not an object; empty or duplicate id; label not a string; value not a number or null; target not finite; more than 3 ranges or not strictly ascending finite numbers; more than 3 rangeLabels or not strings; color not 1-8; invalid valueAxis (max <= min, tickCount outside 2-20). |
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION |
configuration | Invalid or unknown configuration. |
A failed construction leaves the host untouched.
Known limitations
Section titled “Known limitations”- Horizontal rows on one shared axis; one measure color; at most three ranges.
legend,patterns,hiddenSeries,valueAxis.label, andvalueAxis.includeZerohave no effect.- Manual screen-reader verification is pending.
Unsupported scenarios
Section titled “Unsupported scenarios”- Vertical bullets, per-row axes, comparative (second) measures.
- More than three ranges.
- HTML in labels.