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
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
markerdraws 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
limitand 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.
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.
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.
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`.
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`.
API reference
| Part | Built on | Description |
|---|---|---|
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.
| Prop | Type | Default | Description |
|---|---|---|---|
value* | number | null | The bar's length on the scale. null or 0 draws no fill. | |
max* | number | The scale's end. | |
marker | number | A reference value, drawn as a dashed line when it's on the scale. | |
size | keyof typeof barSizes | "default" | "xs" is a hairline, for dense rows. |
BarList
| Prop | Type | Default | Description |
|---|---|---|---|
items* | BarListItem[] | ||
format | Intl.NumberFormatOptions | Intl.NumberFormat options for values. Without a notation, 10,000 and up read compact. | |
locale | string | "en-US" | A BCP 47 locale for numbers. Default "en-US". |
sort | "descending" | "ascending" | "none" | "descending" | Default "descending". "none" keeps the given order. |
limit | number | Shows only the first rows after sorting. | |
total | number | The whole population's total, to show each row's share of it. | |
max | number | The 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. | |
marker | BarListMarker | A reference value drawn across every bar, like a quota, with its label. | |
color | 1 | 2 | 3 | 4 | 5 | 6 | 1 | Which chart color the bars take, --chart-1 to --chart-6. Default 1. |
emptyMessage | string | "No data" | What the list says when it has no rows. Default "No data". |
selected | string | null | The selected row's id, controlled. Any of the three selection props makes rows toggle buttons. | |
defaultSelected | string | null | null | The row to start selected when uncontrolled. |
onSelectedChange | (id: string | null) => void | Called with the toggled row's id, or null when it's cleared. |
BarListItemtype
| Member | Type | Default | Description |
|---|---|---|---|
id | string | A stable id for selection and keys. Default the label. | |
label* | string | What the row is, like an account name. | |
value* | number | null | The number. null is a missing value: it sorts last and reads "No data". | |
detail | string | A second line under the label, like the owner. | |
href | string | Makes the row a link. Ignored when the list is selectable. |
BarListMarkertype
| Member | Type | Default | Description |
|---|---|---|---|
value* | number | Where the line sits, on the bars' scale. | |
label | string | Its 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.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
marker | { value, label? } (added) | none | A 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) | none | The scale's end, so lists side by side compare. Grows to fit the largest value and the marker. |
color | 1, 2, 3, 4, 5, 6 (added) | none | The bars' chart color, --chart-1 by default, on the progress bar's track. |
selected, defaultSelected, onSelectedChange | (an item id, or null) (added) | none | Any 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) | none | Makes the row a link. Ignored when the list is selectable. |
items[].value | number, null (added) | none | null is a missing value: it sorts last and reads No data. |
Accessibility
- Name the list:
aria-label, oraria-labelledbywith 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-pressedchanges 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
nullfor a value you don't have, never a zero. Zero is a real value and draws no bar. - Pass
totalonly 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
hreffor record links or selection for filtering, never both. - Pass
markeronly 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 onecolorper measure across a page. - Never pass
asChildorrender.
Related
- ChartDraws bar, line, area, donut, scatter and funnel charts.
- MetricShows one number with its name, a unit and a trend colored good or bad.
- Data tableShows records in a table that sorts, selects, runs row actions and loads more.
- GaugeShows one value against a maximum, as a dial, a ring, a bar or a row of dots.