Flutter docs
Default skin

TargetBars Composition

Actual against target, one row per thing — a bullet chart. It answers a question asked per category rather than over time: which finished jobs made the margin they were priced at, and by how much did the others miss.

Usage

  • House extension, Jl. MelatiActual 18,2%Target 15,0%
    closed 2 Sep
  • Shophouse fit-out, Block CActual -3,5%Target 15,0%
    closed 28 Aug
  • Warehouse roofActual 11,4%Target 15,0%
    closed 20 Aug
  • Kitchen renovationActual unknownTarget 15,0%
    last invoice not issued

One scale, and it contains zero

Every row is drawn on the same scale, and that scale always includes zero. Scaling each row to its own range — or starting the axis at the smallest value — makes a 2% margin look like a 20% one, which is the failure this chart exists to prevent. A row that lost money draws to the left of the zero line, so a loss reads as a loss and not as a short bar.

The target is a mark on the same track, so how far off is a distance the eye can compare across rows.

Unknown is not zero

A figure that cannot be known yet — a job waiting for its last invoice — is null. It is written as unknown and draws no bar; the target mark still shows. Drawing it as zero would state that the job earned nothing, which is a claim about the business the data does not make.

Higher or lower is better

better decides which side of the target counts as met. The default is higher — a margin, a score. Pass lower for a cost, a delay, a defect rate. Meeting the target exactly counts as met.

  • Site AActual 26 daysAllowed 30 days
  • Site BActual 41 daysAllowed 30 days
  • Site CActual 45 daysAllowed 45 days

Deviation from each row's own target

When the rows have different targets, the bullet makes the reader subtract to see who missed by most. variant="deviation" draws the gap instead: the centre line is the target, a bar runs right when the row beat it and left when it missed, and the scale is symmetric so a 5-point miss and a 5-point beat are the same length. Sort the rows worst first when the question is "which ones do I look at". Both figures are still written; a row with either figure unknown draws no bar.

  • Shophouse fit-out, Block CActual -3,5%Target 15,0%
  • Warehouse roofActual 11,4%Target 20,0%
  • Office partitionActual 12,6%Target 12,0%
  • House extension, Jl. MelatiActual 18,2%Target 15,0%

No currency

The library holds no unit, currency or locale. format is a function the product passes, so the same chart serves a percentage, rupiah or days — and the product's decimal mark stays the product's choice.

Interaction contract

Read-only. No hover, no selection, no dismissal. Every figure is already written out, so there is nothing a tooltip would add.

Accessibility

A <ul> named by label, one <li> per row, each stating its label and both figures as text. The bars are aria-hidden: the sentence already says what they show, and a screen reader reading "bar, 62 percent width" adds noise, not information. Under forced-colors: active the bars keep system colours (Highlight, CanvasText) rather than vanishing with their backgrounds; under prefers-reduced-motion: reduce they are drawn at their final length instead of growing into place.

Do

Use it to compare an outcome against its target across a handful of categories.

Don't

Use it for a series over time — that is TimeSeriesChart, or ProgressCurve for a plan against progress.

Props

PropTypeDefaultNotes
labelstring—Accessible name for the list. Required.
rowsTargetBarsRow[]—One row per category, drawn in the order given.
format(value: number) => string—How a figure is written. The product owns this.
better'higher' | 'lower''higher'Which side of the target counts as met.
variant'bullet' | 'deviation''bullet'Actual from zero with a target mark, or the gap from each row's own target.
actualLabelstring'Actual'Word before the actual figure.
targetLabelstring'Target'Word before the target figure.
unknownLabelstring'unknown'Written where a figure is `null`.
emptyLabelstring'Nothing to compare yet.'Shown instead of the list when there are no rows.
classNamestring—Added to the root element.

TargetBarsRow:

PropTypeDefaultNotes
idstring—Stable key for the row.
labelstring—What the row is.
targetnumber | null—What it was meant to reach; `null` when none was set.
actualnumber | null—What it reached; `null` when it cannot be known yet.
detailReactNode—A second line under the label: a date, a reason, a group.

Tokens

--cb-tone-success and --cb-tone-danger fill a met or missed bar, --cb-fg-subtle an unknown verdict, --cb-fg the target mark (and the deviation centre line) and the actual figure, --cb-fg-muted the other text, --cb-border the zero line, --cb-mini-chart-track-strength the track, --cb-radius-sm, --cb-border-width, --cb-font-sans, --cb-text-sm, --cb-text-xs, --cb-space-*, and --cb-duration-deliberate with --cb-ease-entrance for the bars growing in.