Bar list
Ranked horizontal bars with the label on the bar and the value on the right. For top sources, pages, countries or customers.
npx prodular-ui add bar-listTop sources
Icons, column headers, each row's share of the total and Show more after five rows.
"use client"
import { BookOpenIcon, GlobeIcon, LinkIcon, MailIcon, SearchIcon, SparklesIcon, UsersIcon } from "lucide-react"
import { BarList } from "@/components/ui/bar-list"import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
const sources = [ { name: "Google search", value: 18420, icon: <SearchIcon /> }, { name: "Direct", value: 9310, icon: <GlobeIcon /> }, { name: "ChatGPT and other assistants", value: 6140, icon: <SparklesIcon /> }, { name: "Partner referrals", value: 3880, icon: <UsersIcon /> }, { name: "Product newsletter", value: 2210, icon: <MailIcon /> }, { name: "Help center", value: 1460, icon: <BookOpenIcon /> }, { name: "Other links", value: 920, icon: <LinkIcon /> },]
export default function BarListSources() { return ( <Card className="w-full max-w-md"> <CardHeader> <CardDescription>Signups by source, last 30 days</CardDescription> <CardTitle className="text-2xl font-semibold tracking-tight tabular-nums">42,340</CardTitle> </CardHeader> <CardContent> <BarList data={sources} label="Source" valueLabel="Visitors" limit={5} showShare /> </CardContent> </Card> )}Selectable rows
With onValueChange the rows become buttons. activeKey marks the selected one, here driving the card title.
"use client"
import * as React from "react"
import { BarList, type BarListItem } from "@/components/ui/bar-list"import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
const customers: BarListItem[] = [ { key: "northwind", name: "Northwind Logistics", value: 128400 }, { key: "halcyon", name: "Halcyon Robotics", value: 96250 }, { key: "kestrel", name: "Kestrel Health", value: 61900 }, { key: "lumen", name: "Lumen Analytics", value: 38100 }, { key: "fable", name: "Fable & Finch", value: 22940 },]
const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 })
export default function BarListCustomers() { const [active, setActive] = React.useState("northwind") const selected = customers.find((c) => c.key === active) return ( <Card className="w-full max-w-md"> <CardHeader> <CardDescription>Revenue by customer, Q3</CardDescription> <CardTitle className="text-base">{selected?.name}</CardTitle> </CardHeader> <CardContent> <BarList data={customers} color="var(--chart-2)" valueFormatter={(v) => usd.format(v)} activeKey={active} onValueChange={(item) => setActive(item.key ?? item.name)} /> </CardContent> </Card> )}Loading and empty
Skeleton bars while data loads and a quiet message when there is none.
Loading
Empty
No signups in this period
"use client"
import { BarList } from "@/components/ui/bar-list"
export default function BarListStates() { return ( <div className="grid w-full max-w-2xl gap-10 sm:grid-cols-2"> <div className="flex flex-col gap-3"> <p className="text-[13px] font-medium text-muted-foreground">Loading</p> <BarList data={[]} loading limit={4} /> </div> <div className="flex flex-col gap-3"> <p className="text-[13px] font-medium text-muted-foreground">Empty</p> <BarList data={[]} empty="No signups in this period" /> </div> </div> )}Anatomy
<BarList data={[{ name: "Google search", value: 18420 }]} label="Source" valueLabel="Visitors" limit={5}/>Guidance
Use it when
- Rankings of one measure across many categories: top pages, sources, countries, customers.
- Next to a chart in a dashboard card, for the breakdown behind a headline number.
- onValueChange when picking a row filters the rest of the page.
Avoid
- Values over time. Use Chart with an area or line.
- Comparing two measures per row. Use a table or a grouped bar chart.
- Negative values. Bars grow from the left only.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Pass data as { name, value }[] and let sortOrder sort it. Keep sortOrder="none" only when the order means something, like funnel steps.
- Format values with valueFormatter (currency, percentages), never by passing strings as value.
- Use limit={5} for long lists so the card keeps its height. Show more expands in place.
- Set color to a chart token, var(--chart-N), never a hex value.
- Show loading while data is fetched and pass empty with a sentence that names the period.
API
BarList
| Prop | Type | Default | Description |
|---|---|---|---|
| data | { name, value, key?, href?, icon?, color? }[] | Rows. The longest bar is the largest value. | |
| valueFormatter | (value: number) => ReactNode | Formats the value on the right. | |
| sortOrder | "descending" | "ascending" | "none" | "descending" | How rows are ordered. |
| showShare | boolean | false | Adds each value's percent of the total. |
| limit | number | Rows shown before Show more. | |
| onValueChange | (item) => void | Makes rows buttons. | |
| activeKey | string | Key of the selected row. | |
| color | string | "var(--chart-1)" | Bar color for every row. |
| loading | boolean | false | Shows skeleton bars. |
| label / valueLabel | ReactNode | Column headers over labels and values. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add bar-list# or, with the registry in components.jsonnpx shadcn@latest add @prodular/bar-list