Skip to content

Bar list

Ranks labeled values with a bar under each on one scale, with shares of a total. Rows can link to records or toggle to filter a page.

Installation

pnpm dlx shadcn@latest add @wisty/bar-list

The CLI installs the Radix or Base UI file to match your project, with what it builds on: metric.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • To rank a handful of things by one value: top accounts by pipeline, owners by bookings, queues by backlog, agent topics by escalations. It answers "which ones drive this number".
  • When a row should open its record, or filter the rest of a page.
  • When every row is measured against one reference value, like a quota each rep carries. The marker draws it across every bar, as the Against a quota example shows.
  • Lightning's dashboards draw this as a horizontal bar chart, and the bar list draws that chart as a list.

Don't use it

  • For a trend over time. Use Chart, or Metric with a sparkline.
  • For values that can be negative. Bars here start at zero and only grow right. Use a Chart bar chart.
  • For more than about ten rows. Show the top rows with limit and link to a report or list view for the rest.
  • For parts of a whole that people compare by proportion only. A Chart donut or stacked bar may suit better.
  • For a table of several columns. Use Data table.

Usage

import { BarList } from "@/components/wisty/bar-list"
<BarList
  aria-label="Cases by channel"
  items={[
    { label: "Email", value: 412 },
    { label: "Phone", value: 288 },
    { label: "Web", value: 176 },
  ]}
/>

A row with an href is a link. Passing selected, defaultSelected or onSelectedChange makes every row a toggle button instead, one selected at a time or none, and the list ignores href: the page owns the selection and filters its other parts with it (the Filter by a row example). BarListBar is one bar on its own, for a row a list can't hold (the One bar example); it's hidden from screen readers, so write the number beside it. Pass locale for the org's locale, and format a number you write yourself the same way, with Metric's formatMetricValue; the examples use en-US.

Examples

Filter by a row

A channel selected with `selected` and `onSelectedChange`, named in the card's description, as a page would filter its other parts by it.

New cases by channel
Filtered to Phone: 318 cases

Agent escalations

The top five of twelve topics, kept with `limit`, each with its share of all 214 escalations from `total`, not only of the rows shown.

Escalations by topic
Service agent, September. Top 5 of 12 topics, share of all 214 escalations.
  1. Billing disputes28 sessions with a refund over the agent's limit 61 29%
  2. Order changes after shipping 44 21%
  3. Warranty claims 37 17%
  4. Account access 22 10%
  5. Product recommendations 9 4%

Against a quota

Closed won by rep against the quota every rep carries, drawn across every bar by `marker`, with its text under the rows.

Closed won by rep
Q3 FY2027, against each rep's quota
  1. Maria ChenWest $512K
  2. Priya NairWest $441K
  3. Jon AmosCentral $386K
  4. Lauren BoyleWest $298K
  5. Sam OrtizCentral $214K

One bar

A `BarListBar` under each of two account fields: the default bar, and a `size="xs"` bar with a `marker`, colored by `--bar-list-color`.

Edge Communications
Owner: Maria Chen
Share of open pipeline17%
Win rate62%, target 80%

Zero, small and missing values

A tiny value keeps its true length, a zero has no bar, a `null` value sorts last and says No data, and an empty list shows its `emptyMessage`.

Bookings by region
  1. North America $2.31M
  2. EMEA $1.18M
  3. APAC $24K
  4. LATAM $0
  5. Middle East No data
Renewals by region
  1. No renewals closed yet

API reference

PartBuilt onDescription
BarListBar<span> (opens in a new tab)One bar on a scale from 0 to max. Hidden from assistive technology: the row's text says the value.
BarList<ol> (opens in a new tab)

BarListBar

One bar on a scale from 0 to max. Hidden from assistive technology: the row's text says the value.

PropTypeDefaultDescription
value*number | nullThe bar's length on the scale. null or 0 draws no fill.
max*numberThe scale's end.
markernumberA reference value, drawn as a dashed line when it's on the scale.
sizekeyof typeof barSizes"default""xs" is a hairline, for dense rows.

BarList

PropTypeDefaultDescription
items*BarListItem[]
formatIntl.NumberFormatOptionsIntl.NumberFormat options for values. Without a notation, 10,000 and up read compact.
localestring"en-US"A BCP 47 locale for numbers. Default "en-US".
sort"descending" | "ascending" | "none""descending"Default "descending". "none" keeps the given order.
limitnumberShows only the first rows after sorting.
totalnumberThe whole population's total, to show each row's share of it.
maxnumberThe scale's end, shared by lists side by side so their bars compare. Grows to fit the largest value and the marker if they're larger.
markerBarListMarkerA reference value drawn across every bar, like a quota, with its label.
color1 | 2 | 3 | 4 | 5 | 61Which chart color the bars take, --chart-1 to --chart-6. Default 1.
emptyMessagestring"No data"What the list says when it has no rows. Default "No data".
selectedstring | nullThe selected row's id, controlled. Any of the three selection props makes rows toggle buttons.
defaultSelectedstring | nullnullThe row to start selected when uncontrolled.
onSelectedChange(id: string | null) => voidCalled with the toggled row's id, or null when it's cleared.

BarListItemtype

MemberTypeDefaultDescription
idstringA stable id for selection and keys. Default the label.
label*stringWhat the row is, like an account name.
value*number | nullThe number. null is a missing value: it sorts last and reads "No data".
detailstringA second line under the label, like the owner.
hrefstringMakes the row a link. Ignored when the list is selectable.

BarListMarkertype

MemberTypeDefaultDescription
value*numberWhere the line sits, on the bars' scale.
labelstringIts name, shown before its value under the list, like "Quota".

SLDS mapping

SLDS has no bar list. The rows follow the SLDS Charts guidelines and the theme's text tokens; each bar is SLDS's circular progress bar (slds-progress-bar slds-progress-bar_circular) in a chart color on the progress track, the same bar the flat Gauge draws, and BarListBar's xs size is slds-progress-bar_x-small. The marker is the flat gauge's target mark, drawn on each bar. A data-chart-palette on an ancestor changes the bars' color.

Known gaps:

  • SLDS's bar charts have an axis, gridlines and a tooltip. The bar list has none of them and writes the value on every row instead.
PropValueSLDSNotes
marker{ value, label? } (added)noneA reference value, like a quota: a dashed line across every bar, as the flat gauge's target, with its text once under the rows.
max(a number) (added)noneThe scale's end, so lists side by side compare. Grows to fit the largest value and the marker.
color1, 2, 3, 4, 5, 6 (added)noneThe bars' chart color, --chart-1 by default, on the progress bar's track.
selected, defaultSelected, onSelectedChange(an item id, or null) (added)noneAny of the three makes rows toggle buttons with aria-pressed; the other rows' bars fade. A selectable list ignores href.
items[].href(a URL) (added)noneMakes the row a link. Ignored when the list is selectable.
items[].valuenumber, null (added)nonenull is a missing value: it sorts last and reads No data.

Accessibility

  • Name the list: aria-label, or aria-labelledby with the title's id. Without one it reads as an unnamed list.
  • The bar is hidden from assistive technology; the row's text carries the value.
  • A long label is cut with an ellipsis and keeps its full text in a title. The cut is visual only: a linked row's accessible name holds the whole label.
  • Selection is never shown by color alone: the row's aria-pressed changes and the other bars fade.

Motion

  • When the selection changes, the other bars fade in every theme. With reduced motion they change at once.

Responsive behavior

  • The label column shrinks and truncates; the value and share never wrap. On a narrow card a long name loses its end first, so keep the figures short with format.

Rules

  • Pass null for a value you don't have, never a zero. Zero is a real value and draws no bar.
  • Pass total only when the values add up to it (amounts and counts, not rates), and only the true total of all records, never the sum of the rows shown.
  • Use href for record links or selection for filtering, never both.
  • Pass marker only for one reference that applies to every row, in the values' unit. A target that differs per row isn't a marker; use a Gauge per row.
  • Give lists side by side the same max, at least their largest value. Keep one color per measure across a page.
  • Never pass asChild or render.