Bar listFree
Named values ranked largest first, each with a bar sized to the largest; a real list, with optional links. Plain HTML: no chart engine.
npx shadcn@latest add https://beautifulcharts.dev/r/bar-list.jsonUsage
A complete client module. Install the item first, then paste it into your app.
"use client";
import { BarList } from "@/components/beautiful-charts/themes/instrument/bar-list";
const sources = [
{ source: "Search", visits: 4820, href: "/analytics/search" },
{ source: "Direct", visits: 3110, href: "/analytics/direct" },
{ source: "Newsletter", visits: 1290, href: "/analytics/newsletter" },
{ source: "Partners", visits: null, href: "/analytics/partners" },
];
// Ranked largest first. The row with no value is listed with a dash and no bar, never as zero.
export function TrafficSources() {
return <BarList data={sources} nameKey="source" valueKey="visits" hrefKey="href" label="Traffic sources" showShare />;
}
Props
data*
TDatum[]
Your rows.
nameKey*
keyof TDatum & string
The row's name.
valueKey*
keyof TDatum & string
The row's number.
hrefKey
keyof TDatum & string
Optional link for each row: the name becomes a link.
sort
"descending" | "ascending" | "none"
"descending" (default), "ascending", or "none" to keep your order. Missing values always go last.
label
string
What the list is, e.g. "Top pages". Names it for screen readers.
| Prop | Type | Description |
|---|---|---|
| data* | TDatum[] | Your rows. |
| nameKey* | keyof TDatum & string | The row's name. |
| valueKey* | keyof TDatum & string | The row's number. |
| hrefKey | keyof TDatum & string | Optional link for each row: the name becomes a link. |
| sort | "descending" | "ascending" | "none" | "descending" (default), "ascending", or "none" to keep your order. Missing values always go last. |
| label | string | What the list is, e.g. "Top pages". Names it for screen readers. |
Sourcecomponents/beautiful-charts/themes/instrument/bar-list.tsx
Show the source (52 lines)
"use client";
import { useEffect, useState, type CSSProperties } from "react";
import { useBarListModel, type BarListProps } from "../../core/bar-list";
import { InstrumentStyles } from "./styles";
export type BarListChartProps<TDatum extends Record<string, unknown>> = BarListProps<TDatum> & {
/** The bar colour; defaults to the theme's first palette colour. Any CSS colour or var(). */
color?: string | undefined;
/** Classes on the list. */
className?: string | undefined;
};
/** Sample data used when you install the bar list; replace it with your own rows. */
export const barListSample = [
{ page: "/pricing", visits: 4120 }, { page: "/docs/installation", visits: 3380 }, { page: "/components/bar-chart", visits: 2650 },
{ page: "/blocks/saas-dashboard", visits: 1790 }, { page: "/changelog", visits: null }, { page: "/about", visits: 640 },
];
/**
* Bar list, Instrument theme. Plain HTML: no chart engine.
* Named values ranked largest first, each with a bar behind its name sized to the largest. It's a real list, so
* screen readers read every name and value in order; rows with a link are ordinary links. A missing value is a dash
* with no bar; a negative one is listed but not drawn.
*/
export function BarList<TDatum extends Record<string, unknown>>(props: BarListChartProps<TDatum>) {
const m = useBarListModel(props);
const [entered, setEntered] = useState(false);
// Entered on the next frame (not synchronously in the effect), so the entry animation has a first frame to start from.
useEffect(() => { const frame = requestAnimationFrame(() => setEntered(true)); return () => cancelAnimationFrame(frame); }, []);
return (
<div data-slot="bar-list" data-bc-theme={"Instrument".toLowerCase()} data-entered={entered && m.k > 0 ? "" : undefined} className={props.className}
style={{ ["--bc-k" as string]: m.k, ...(props.color ? { ["--bar-list-color" as string]: props.color } : {}) } as CSSProperties}>
<InstrumentStyles />
<ul data-slot="bar-list-rows" aria-label={m.summary}>
{m.rows.map((row, i) => (
<li key={row.key} data-slot="bar-list-row" data-missing={row.value === null ? "" : undefined} style={{ ["--w" as string]: row.width ?? 0, ["--c" as string]: i } as CSSProperties}>
{row.width !== null ? <span data-slot="bar-list-bar" aria-hidden="true" /> : null}
<span data-slot="bar-list-name">{row.href ? <a href={row.href}>{row.name}</a> : row.name}</span>
<span data-slot="bar-list-value">
{row.text}
{props.showShare && row.share !== null ? <span data-slot="bar-list-share"> · {m.f.percent(row.share, row.share < 0.1 ? 1 : 0)}</span> : null}
</span>
</li>
))}
</ul>
{m.note ? <p data-slot="bar-list-note">{m.note}</p> : null}
{!m.rows.length ? <p data-slot="bar-list-note">No data yet.</p> : null}
</div>
);
}
Details
The other 5 bar list props
showShare
boolean
Show each row's share of the total next to its value.
valueFormatter
(value: number) => string
Formats the values.
motion
MotionPreference
"full" (default), "subtle" or "off". Reduced-motion settings always win.
color
string
The bar colour; defaults to the theme's first palette colour. Any CSS colour or var().
className
string
Classes on the list.
| Prop | Type | Description |
|---|---|---|
| showShare | boolean | Show each row's share of the total next to its value. |
| valueFormatter | (value: number) => string | Formats the values. |
| motion | MotionPreference | "full" (default), "subtle" or "off". Reduced-motion settings always win. |
| color | string | The bar colour; defaults to the theme's first palette colour. Any CSS colour or var(). |
| className | string | Classes on the list. |
Props every chart shares (2): locale, timeZone
locale
string
BCP 47 locale. Defaults to "en-US" so server and client render the same text.
timeZone
string
IANA time zone for dates. Defaults to "UTC" so server and client agree.
| Prop | Type | Description |
|---|---|---|
| locale | string | BCP 47 locale. Defaults to "en-US" so server and client render the same text. |
| timeZone | string | IANA time zone for dates. Defaults to "UTC" so server and client agree. |
- Pointer
- Rows with a link are ordinary links; otherwise not interactive.
- Keyboard
- No tab stops of its own; a row with a link is one ordinary link.
- Screen readers
- A list named by one sentence (how many rows, which leads and its share, anything missing or negative); every row's name and value is read in order.
- Motion
- The bars grow from the left, one after another. With reduced motion it appears settled at once.
Related
- Bar chartGrouped, stacked or 100%, vertical or horizontal, with a measured callout and a computed takeaway
- Radar chartProfiles across axes with per-axis ranges, honest gaps and an optional benchmark
- Scatter chartPoints by two numbers, bubbles sized by area, groups, log axes, quadrants and trend lines; rows it can't place are counted