Legend list
Flexible-right-slot rank list used inside data cards (Conversion rate by payment method, Cards approval by provider, Chargebacks rate by provider and card brand). Each row = dot + label + optional secondary sub-label + right slot (canonically a <Badge>). When onViewPaymentsRow is wired, hovering a row swaps the right slot for a 'View payments' link CTA. When items.length > maxRows AND viewMoreTitle is set, the list truncates and a 'View more (N)' Button appears below, opening a Dialog with the full list.
Anatomy
A vertical stack of LegendRows. Each row has two clusters: LEFT — a colored dot + primary label + optional muted secondary sub-label; RIGHT — the caller-provided `right` slot (usually a <Badge>). When drilldown is enabled, hovering a row swaps the right slot for a 'View payments' CTA using a grid overlay so the row height/width never changes.
- Stripe89.13%
- Adyen84.51%
- Braintree79.20%
- dLocalCard75.02%
- 1Dot
size-2 rounded-full swatch on the left. Color comes from item.color — prefer var(--chart-N) / semantic tokens.
- 2Label + secondary
text-sm foreground primary label that truncates. Optional muted secondary follows the label (ml-2), also truncating.
- 3Right slot
Caller-provided ReactNode. Canonical: a <Badge variant="secondary"> with the metric value (%, count, currency).
- 4Hover CTA
When onViewPaymentsRow is defined and the item carries `filter`, hovering the row swaps the right slot for a small 'View payments' link Button with an ArrowRight icon. Same grid-overlay technique used across Insights so the row never shifts.
- 5View more (N)
When items.length > maxRows AND viewMoreTitle is set, a link Button appears centered below the last row. Clicking it opens a Dialog with the full list.
Recipes
The minimal shape. Pass items with a Badge in each item.right. No truncation, no drilldown. Use when the surrounding card already conveys the metric or when the list is short.
- Stripe89.13%
- Adyen84.51%
- Braintree79.20%
- dLocalCard75.02%
<LegendList items={items} />
// items: LegendItem[] = [
// { key, label, color, right: <Badge>{pct}%</Badge>, secondary?, filter? },
// ]Wire onViewPaymentsRow to reveal a 'View payments' CTA on rows that carry a `filter`. The CTA replaces the right slot on hover using a grid overlay so the row width/height never changes.
- Stripe89.13%
- Adyen84.51%
- Braintree79.20%
- dLocalCard75.02%
<LegendList
items={items}
onViewPaymentsRow={(row) => navigate({ dimensions: row.filter })}
/>Truncates at maxRows (default 6) and renders a link Button 'View more (N)' below that opens a Dialog with the full list. Combine with onViewPaymentsRow so the Dialog rows also carry the hover CTA.
- Stripe89.13%
- Adyen84.51%
- Braintree79.20%
- dLocalCard75.02%
- Cybersource71.44%
- PayU68.31%
<LegendList
items={items} // 9 rows
viewMoreTitle="Approval rate by provider"
maxRows={6} // default
onViewPaymentsRow={handler}
/>Import
Copy this import line at the top of the file where you compose this atom.
import { LegendList, type LegendItem } from "@/components/ui/legend-list";Props
Everything else from the underlying HTML or Radix primitive is forwarded via ...props.
LegendList
| Prop | Type | Default | Description |
|---|---|---|---|
| items | LegendItem[] (required) | — | The rows to render. See LegendItem below for the item shape. |
| onViewPaymentsRow | (item: LegendItem) => void | — | When defined, rows with `filter` reveal a "View payments" link CTA on hover that replaces the `right` slot. The handler fires with the whole item on click. |
| viewMoreTitle | string | — | When set AND items.length > maxRows, the list truncates and a "View more (N)" Button appears below, opening a Dialog with the full list. Without this prop, all items render. |
| maxRows | number | 6 | Max rows shown before truncation kicks in. Ignored when `viewMoreTitle` is not set. |
| className | string | — | Extra classes on the outer <div>. |
LegendItem
| Prop | Type | Default | Description |
|---|---|---|---|
| key | string (required) | — | Stable key used for React reconciliation. |
| label | string (required) | — | Primary label (dot + label row, truncates). |
| color | string (required) | — | Dot color. Prefer var(--chart-N) / semantic tokens over hex. |
| right | ReactNode | — | Right-slot content when no hover CTA is active. Canonical: a <Badge variant="secondary"> with the metric value (%, count, currency). |
| secondary | ReactNode | — | Muted sub-label after the primary label (e.g. "Card" under "dLocal"). |
| filter | Record<string, string | number> | — | Per-row drilldown filter. When present + onViewPaymentsRow wired, the row reveals "View payments" on hover. |
Related
Cross-links to atoms and patterns you may reach for next.
- Chart cardCanonical host — LegendList typically goes inside ChartCard's body as a data-card variant.
- BadgeCanonical right-slot component. Use variant="secondary" for the metric pill (or any custom shape for the row's right cluster).