Skip to content
Folio

Data

BarList

A ranked list, with the bar behind the name rather than beside it.

Usage

When to reach for it

Top referrers, slowest endpoints, biggest accounts. A horizontal BarChart spends a third of its width on an axis repeating labels the rows could simply contain.

Ships from@misoto22/design/charts

TSX
import { BarList } from '@misoto22/design/charts'

Notes

A ranked list, with the bar behind the name rather than beside it.

The answer to "top referrers", "slowest endpoints", "biggest accounts" — and a better one than a horizontal bar chart, which spends a third of its width on a category axis repeating labels the rows could simply contain. Reading a name off a y-axis and matching it to a bar is two steps; reading it off the bar is none.

It is a <table>, because that is what it is: two columns, a header, and one row per thing. The bar is a background on the name cell, so it never becomes a second element a screen reader has to walk past.

Reach for <BarChart orientation="horizontal"> instead when the categories are few and fixed and the axis is doing real work — a scale a reader needs to read values off, rather than a ranking they need to skim.

Anatomy

BarList anatomy
ElementDescription
Captionrequiredlabel, as the table’s real caption — sr-only unless showLabel prints it as an eyebrow above the rows. It is what names the list for a screen reader.
Header rowrequiredAn sr-only thead of two th scope="col" cells, Name and Value, so both columns are named even though the list never shows a header.
Name cellrequiredA th scope="row" holding the row’s name truncated to one line, with item.icon before it as aria-hidden decoration and item.href turning the name itself into the link.
BarrequiredNot an element: a linear-gradient on the name cell’s inner span, a hard stop at the row’s share of the ceiling. Drawn as a sibling div it would be one more empty thing in the accessibility tree saying nothing.
Value cellrequiredA td at the end edge in mono tabular figures at --ink-2, written by formatValue — the same compact default the axes use unless the call site replaces it.
Other rowWhat limit adds: one final row named Other carrying the summed tail, so the rows shown still account for the whole they were cut from.
Empty stateChartEmpty under the label when there is nothing to rank. A caption over an empty tbody is a list that failed to load as far as the reader can tell, and reloading does not change it.

Best practices

Do

  • Pin max when rows arrive over time. The ceiling is the largest row currently shown, so one new leader rescales every bar under it — a track that was two thirds full yesterday is a quarter full today with exactly the same number in it, and the movement the reader sees happened to a different row.
  • Reach for limit rather than slicing items at the call site. The tail is summed into the Other row instead of being dropped, so the five rows shown still add up to the whole; a top five cut by hand discards the other forty silently, and nothing in the list says so.
  • Keep names short enough to survive the truncation. The name is a single truncated line sharing its width with the value column, so two endpoints that differ only past the fortieth character render as the same row with the same ellipsis.
  • Pass sort={false} when the order is the point — a funnel, a set of steps, a fixed set of regions. sort defaults to true and orders descending, which turns a sequence into a ranking without saying that it did.

Don’t

  • The bar is the row over the largest row, never a share of a total: the leading row always fills its track, so five rows that make up 3 percent of traffic look exactly like five that make up all of it. Where the whole matters, the caption is where it goes.
  • Do not mix number shapes down the value column. It is end-aligned mono with tabular figures, which lines digits up only while the strings are the same shape — and the default formatter switches to the compact form at 10,000, so a list spanning that threshold puts 9,400 under 1.2M and there is nothing left to compare down.
  • Two rows cannot share a name. It is the row’s label and its React key at once, so a list built from a query that can repeat a label renders duplicate keys, which React warns about and reconciles wrongly the moment the list updates.

Examples

default

The ranked list at its plainest: eight rows, with the bar drawn behind the name rather than beside it. That is the thing a horizontal bar chart cannot do — it spends a third of its width on a category axis, and matching a name on that axis to its bar is two steps where reading the name off the bar is none. Rows are sorted descending unless sort says otherwise, and the scale's ceiling comes from the largest of them, so this list is readable on its own but not against a second one.

Top referrers
NameValue
google.com42.1K
github.com18.8K
news.ycombinator.com9,400
x.com6,120
reddit.com3,280
lobste.rs1,940
bsky.app1,100
linkedin.com640

limit

limit keeps the top three and sums the remaining five into one Other row rather than dropping them. A top three that silently discards the tail misstates the whole and gives the reader no way to tell it happened; the Other row is what keeps the column of numbers adding up to something. The three leading rows also carry an href, which turns the name itself into the link — the bar is a background on the same cell, so linking a row adds nothing extra for a screen reader to walk past.

Top referrers
NameValue
google.com42.1K
github.com18.8K
news.ycombinator.com9,400
Other13.1K

a shared ceiling

Two lists that have to be read against each other, so both are given the same max. Left to derive it, each takes its ceiling from its own largest row and Enterprise fills its track in both — a quarter that more than doubled would draw exactly like a quarter that stood still, and nothing on the page would say otherwise. formatValue makes the numbers money; the default is the same compact form the axes use, which is right for a count of visits and silent about a currency.

New ARR this quarter
NameValue
EnterpriseA$92,400
Mid-marketA$61,200
Self-serveA$28,900
EducationA$9,600
New ARR last quarter
NameValue
EnterpriseA$41,800
Mid-marketA$33,500
Self-serveA$26,100
EducationA$11,200

Accessibility

  • A real <table> with two columns and one row per thing, because that is what a ranked list is. The bar is a background on the name cell, so it is never a second element a screen reader has to walk past.
  • limit sums the tail into an “Other” row rather than dropping it — a top five that silently discards the other forty misstates the whole, and the reader has no way to tell.
  • Pin max to compare two lists side by side: on independent scales the leading row of each fills its track, and two very different numbers look identical.