Skip to content
txbrendan/ui

Breakdown List

beta

Rows with their number and a pill bar, inside a card, for parts of a whole or a ranking, with targets, status tones, and change words.

Preview

status

Survey replies by team
  • Design: 92%, target 80%, Up 623 of 25 replied
  • Engineering: 81%, target 80%, Up 217 of 21 replied
  • Support: 64%, target 80%, Down 414 of 22 replied
  • Marketing: no figure, target 80%Not sent yet

currency

Event costs
  • Venue hire: $4,200
  • T-shirts: $2,650
  • Travel: $1,180

people

Tickets closed by person
  • Ava Clarke: 14
  • Sam Okafor: 11
  • Mia Hughes: 9
  • Leo Martin: 6

swatches

  • Starter: 38
  • Team: 31
  • Business: 22

loading

Survey replies by team

Loading

empty

Survey replies by team

No replies yet.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/breakdown-list

Use it for

  • Parts of a whole or a ranking with readable labels, like survey replies by team or tickets closed by person.
  • Pass items, each with a label and a value, and a title or aria-label.
  • Pass max for the value of a full bar, like 100 for percents. It defaults to the largest value.
  • Give an item a target to draw a tick on its bar, and tone='success' or 'warning' when its value is a status.
  • Give an item a change, like { text: 'Up 6', tone: 'success' }, for a short word on the right.
  • Put an icon, a person's photo, or a color swatch in icon.
  • Pass value: null when a row has no figure. It shows a dash, not a zero.

Avoid it for

  • Steps that narrow, like sign-ups to purchases. Use FunnelChart.
  • A trend over time. Use LineChart or AreaChart.
  • Many categories compared at once with several series. Use BarChart.
  • More rows than people can scan, like 50. Show the top ten and link to the rest.

Props

  • items

    Required. One row each, in order. A row's tone colors its bar as a status. Left out, the bar takes --chart-1.

    { id?: string; label: string; value: number | null; meta?: ReactNode; icon?: ReactNode; target?: number; tone?: "success" | "warning" | "danger"; change?: { text: string; tone?: "success" | "warning" | "danger" } }[]

  • title

    A visible title. It names the list. TypeScript requires title, aria-label, or aria-labelledby.

    ReactNode

  • aria-label

    Names the list when it has no visible title.

    string

  • max

    The value of a full bar. Default: the largest value.

    number

  • valueFormat

    How values and targets print. Options work from a server component. A function works from a client component only.

    Intl.NumberFormatOptions | ((value: number) => string)

  • locale

    The locale for valueFormat options.

    string · default "en-US"

  • loading

    Shows placeholder rows, as many as items, or three.

    boolean · default false

  • emptyText

    Shown when items is empty.

    ReactNode · default "No data yet"

  • className

    Extra classes for the card. Later classes win, so 'border-0 p-0' drops the card's edge and padding.

    string

Accessibility

  • Renders a <figure> named by its title, or by aria-label. TypeScript requires one. The rows are a list.
  • Each row reads as its label, then its value, its target, and its change, like 'Design: 84%, target 80%, Up 6'.
  • A row with no value reads 'no figure', and shows a dash with no bar, so it never reads as zero.
  • The bars are hidden from screen readers. The track is quiet, so the number in text carries the meaning.
  • Status bars use the tone's color, and plain bars use --chart-1, which the theme holds to 3:1 on the page.
  • Bars grow in once the list is on screen, over three times --motion-duration-base. Reduced motion and motion off draw them at once.
  • Loading is read out through a status line that stays on the page.

Examples

Status bars against a target

<BreakdownList
  title="Survey replies by team"
  max={100}
  valueFormat={{ style: "unit", unit: "percent" }}
  items={[
    { label: "Design", value: 92, target: 80, tone: "success", change: { text: "Up 6", tone: "success" } },
    { label: "Engineering", value: 64, target: 80, tone: "warning" },
    { label: "Marketing", value: null },
  ]}
/>

A ranking with icons

import { Trophy, UserRound } from "lucide-react";

<BreakdownList
  aria-label="Tickets closed by person"
  items={[
    { label: "Ava Clarke", value: 14, icon: <Trophy /> },
    { label: "Sam Okafor", value: 11, icon: <UserRound /> },
  ]}
/>

While it loads

<BreakdownList title="Survey replies by team" items={data ?? []} loading={isLoading} />