Dots

Share Bar

Parts of a whole in one segmented bar with a legend.

  • Shopping$772.9042%
  • Food$515.3028%
  • Transport$331.2018%
  • Other$220.8012%
import { Card, CardContent } from "@/components/ui/card"
import { ShareBar, type ShareBarItem } from "@/components/ui/share-bar"

Use a share bar to show how a total splits into parts, such as spending by category or who paid what on a shared bill. It draws one color at falling density, never several hues, and keeps at most four segments so the bar stays readable. Pick a chart when the parts change over time, and a key-value list when the exact amounts matter more than the proportions.

Installation

pnpm dlx shadcn@latest add @dots/share-bar

Usage

import { ShareBar } from "@/components/ui/share-bar"
<ShareBar
  currency="USD"
  items={[
    { label: "Shopping", value: 772.9 },
    { label: "Food", value: 515.3 },
    { label: "Transport", value: 331.2 },
  ]}
/>

Composition

ShareBar renders the bar and its legend on its own. Pass children to choose the parts yourself:

ShareBar
├── ShareBarTrack
└── ShareBarLegend
    └── ShareBarSwatch

Features

  • One color at falling density, in the order you pass the items
  • At most four segments; the tail is merged into one "Other" segment
  • Whole percents that always add up to 100, with <1% for a small non-zero share
  • Legend values as money when currency is set, or as plain numbers
  • The bar is read by screen readers as one image that lists every share
  • A custom legend row through a render function

Merged tail

Pass the items largest first. When there are more items than maxSegments, the rest are added up into one segment named by otherLabel. The demo at the top passes five categories, so the last two become "Other".

<ShareBar items={spending} maxSegments={3} otherLabel="Everything else" />

Custom legend

Pass a function as the child of ShareBarLegend to render each row yourself. Use ShareBarSwatch with the segment's tone to keep the color key.

  • You paid$820.00
  • Sam paid$410.00
  • Jordan paid$186.00
"use client"

import { MoneyAmount } from "@/components/ui/money-amount"

Counts

Leave out currency to show plain numbers, such as payments by method.

  • Card4264%
  • Transfer1828%
  • Cash58%
import { ShareBar, type ShareBarItem } from "@/components/ui/share-bar"

const payments: ShareBarItem[] = [

Bar only

Render only ShareBarTrack for a compact summary under a heading. Its aria-label is put before the generated list of shares.

Spent in November$1,759.80
import {
  ShareBar,
  ShareBarTrack,

Empty

When every value is 0, the track renders empty, the legend shows 0% for each item and screen readers hear "No data".

  • You$0.000%
  • Sam$0.000%
import { ShareBar } from "@/components/ui/share-bar"

export function ShareBarEmpty() {

Accessibility

ShareBarTrack has role="img" and a name built from the segments, for example "Shopping 42%, Food 28%". The segments themselves are hidden from screen readers. ShareBarLegend is a real list, and ShareBarSwatch is decorative.

Name the bar

Give the track an aria-label that says what is split. It is put before the shares.

<ShareBarTrack aria-label="Spent in November" />

This is read as "Spent in November: Shopping 44%, Food 29%, …".

Meaning beyond color

Segments differ only by density. Keep the legend, or a nearby label with the amounts, so the split is not conveyed by color alone.

API Reference

ShareBar

The root. Works out the segments and shares them with its parts. Renders a div and accepts all of its props.

PropTypeDefaultDescription
itemsShareBarItem[]-The parts, largest first. Color density follows this order. Required.
maxSegmentsnumber4Number of segments, from 1 to 4. Past that the tail is merged.
otherLabelstring"Other"Label of the merged segment.
currencystring-ISO 4217 code. Legend values are shown as money.
localestring"en-US"Locale for numbers and money.
childrenReact.ReactNode-Your own parts. Defaults to ShareBarTrack and ShareBarLegend.
classNamestring-Additional classes to apply to the root.

ShareBarTrack

The bar. Renders a div with role="img" and accepts all of its props except children.

PropTypeDefaultDescription
aria-labelstring-What is split. Put before the generated share list.
classNamestring-Additional classes to apply to the track.

ShareBarLegend

The parts in bar order: swatch, label, value and share. Renders a ul and accepts all of its props.

PropTypeDefaultDescription
children(segment: ShareBarSegment) => React.ReactNode-Renders each row yourself. Each result is wrapped in an li.
classNamestring-Additional classes to apply to the legend.

ShareBarSwatch

The color key of one segment, for custom legend rows. Renders a decorative span.

PropTypeDefaultDescription
tonenumber-The segment's tone. 0 is the densest. Required.
classNamestring-Additional classes to apply to the swatch.

ShareBarItem

One part you pass in items.

FieldTypeDefaultDescription
idstringlabel and positionStable, unique key. Do not use "__other".
labelstring-The part's name. Required.
valuenumber-The part's size in any unit. Negative counts as 0.
valueLabelReact.ReactNode-Shown instead of the formatted value in the legend.

ShareBarSegment

What a custom legend row receives.

FieldTypeDescription
idstringThe item's key, or "__other" for the merged segment.
labelstringThe segment's name.
valuenumberThe segment's size.
valueLabelReact.ReactNodeThe item's valueLabel, if any.
percentnumberWhole percent. All segments add up to 100 when total > 0.
tonenumber0 is the densest; each next segment is lighter.
isOtherbooleanTrue for the merged tail segment.

useShareBar

Reads segments, total, currency and locale inside a ShareBar. Throws outside of one.

formatSharePercent

Formats a segment's share as text: "42%", or "<1%" for a small non-zero share.

formatSharePercent(segment) // "42%"