Gesso
Data display

Segmented bar

A distribution on one 8px track, with an optional legend of counts and shares.

Segmented bar shows how a whole splits into parts, like where a campaign's recipients ended up. It is the multi-value sibling of Progress size="sm": the same 8px track, with each segment its own rounded pill, 1px from the next. Each segment takes a status tone or a category color, and with legend a list under the bar names every part with its count and share. Pointing at a segment highlights its legend row, and pointing at or focusing a row dims the rest of the bar.

  • Delivered1,14895%
  • Deferred00%
  • Bounced413%
  • Not sent151%

Usage

import { SegmentedBar } from '@/components/ui/segmented-bar';

<SegmentedBar
  label="Delivery"
  total={recipients}
  legend
  segments={[
    { label: 'Delivered', value: delivered, tone: 'success' },
    { label: 'Bounced', value: bounced, tone: 'destructive' },
    { label: 'Not sent', value: notSent },
  ]}
/>

Total and bare track

total sets what the widths and shares are out of. It defaults to the sum of the segments, and a larger total leaves bare track, like the recipients still queued while a campaign sends. A total smaller than the sum is raised to the sum, so the segments never overflow the track. Switch between the two snapshots above to see the segments ease to their new widths.

Segments with a value of zero or less are left off the bar and stay in the legend, and a segment that is a sliver of the total is drawn at least 2px wide so it can still be seen. Shares round to whole percents, and a part too small to round up reads as <1% rather than 0%.

Colors

Tones are status roles: success, info, warning, destructive, and neutral, the default. Each status segment sets the matching tone, where destructive is the danger tone, and neutral is a quiet gray with no tone. When the parts are categories rather than outcomes, give each a color from the category palette instead, which wins over tone. The colors are gray, red, orange, amber, yellow, green, blue, purple, and pink.

Opens by client

With or without a legend

With legend, a row under the bar lists each part with its swatch, label, count, and share. Without it, the bar stands alone and its name carries every part, which suits a row where the figures are already written beside it.

Delivery1,148 of 1,204

Motion

RuleWhat happens
Values changeSegments ease to their new widths on --duration-base and --ease-out.
Pointing or focusThe other segments drop to --alpha-32, and the matching legend row fills.
Empty partA zero leaves the bar and stays in the legend at 0, so the list does not reshuffle.
Reduced motionWidths and dimming change without easing.

Props

Segmented bar takes every <div> attribute except children, plus these.

PropTypeDefaultWhat it does
segments{ label, value, tone?, color? }[]The parts, left to right. tone is a status role and color a category hue, which wins over tone.
totalnumberThe sumWhat widths and shares are out of. A larger total leaves bare track.
legendbooleanfalseLists each segment under the bar with its count and share.
labelstringNames the bar, or the legend when there is one.
formatValue(value: number) => stringtoLocaleStringFormats counts in the legend and in the accessible summary.

Accessibility

  • Without a legend, the bar is role="img" with an aria-label that summarizes every part, like "Delivery: Delivered 1,148 (95%), Bounced 41 (3%)". The label comes first when you pass one.
  • With a legend, the bar is hidden from assistive tech and the legend, a list named by label, carries the same facts as text. Give it a label, because a legend with none has no name.
  • Counts and shares are always text, so color never carries a part on its own. The legend swatches are decorative, and the label beside each says what it is.
  • Each legend row takes focus, with a 2px ring, and focusing one highlights its segment the way pointing does. A part with a value of zero has no segment, so the whole bar dims. The segments themselves are not focusable.
  • Changes are not announced, so a bar that updates while a person watches needs a live region of your own.
  • Under reduced motion widths and dimming change without easing, and the legend's fill changes without a transition.
  • In forced colors every segment and swatch draws in CanvasText, split by its 1px gaps, inside a bordered track, so the bar still shows the proportions while the summary or the legend names each part.

When to use something else

  • Progress for one value moving toward a total, like an upload or an import. It is a progressbar that announces its value, and a segmented bar is a snapshot of parts.
  • Stat strip when the exact numbers are the point and the split is secondary.
  • Sparkline for a trend over time, not a split at one moment.
  • A charting library for many parts, axes, or values that need hover detail. Gesso has no chart item.

Install

shadcn add @gesso/segmented-bar

On this page