Components

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.

Updated Aug 6, 2026 by Leonardo Posada

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.

  • Stripe
    89.13%
  • Adyen
    84.51%
  • Braintree
    79.20%
  • dLocalCard
    75.02%
  1. 1
    Dot

    size-2 rounded-full swatch on the left. Color comes from item.color — prefer var(--chart-N) / semantic tokens.

  2. 2
    Label + secondary

    text-sm foreground primary label that truncates. Optional muted secondary follows the label (ml-2), also truncating.

  3. 3
    Right slot

    Caller-provided ReactNode. Canonical: a <Badge variant="secondary"> with the metric value (%, count, currency).

  4. 4
    Hover 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.

  5. 5
    View 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

Basic — static list

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.

  • Stripe
    89.13%
  • Adyen
    84.51%
  • Braintree
    79.20%
  • dLocalCard
    75.02%
<LegendList items={items} />
// items: LegendItem[] = [
//   { key, label, color, right: <Badge>{pct}%</Badge>, secondary?, filter? },
// ]
With hover CTA (drilldown)

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.

  • Stripe
    89.13%
  • Adyen
    84.51%
  • Braintree
    79.20%
  • dLocalCard
    75.02%
<LegendList
  items={items}
  onViewPaymentsRow={(row) => navigate({ dimensions: row.filter })}
/>
With View more (N) Dialog

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.

  • Stripe
    89.13%
  • Adyen
    84.51%
  • Braintree
    79.20%
  • dLocalCard
    75.02%
  • Cybersource
    71.44%
  • PayU
    68.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.

LegendList is a controlled atom. All state (which item is hovered) is internal to the row; the caller only decides whether hover CTAs and View more are enabled.
import { LegendList, type LegendItem } from "@/components/ui/legend-list";

Props

Everything else from the underlying HTML or Radix primitive is forwarded via ...props.

LegendList

PropTypeDefaultDescription
itemsLegendItem[] (required)The rows to render. See LegendItem below for the item shape.
onViewPaymentsRow(item: LegendItem) => voidWhen 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.
viewMoreTitlestringWhen 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.
maxRowsnumber6Max rows shown before truncation kicks in. Ignored when `viewMoreTitle` is not set.
classNamestringExtra classes on the outer <div>.

LegendItem

PropTypeDefaultDescription
keystring (required)Stable key used for React reconciliation.
labelstring (required)Primary label (dot + label row, truncates).
colorstring (required)Dot color. Prefer var(--chart-N) / semantic tokens over hex.
rightReactNodeRight-slot content when no hover CTA is active. Canonical: a <Badge variant="secondary"> with the metric value (%, count, currency).
secondaryReactNodeMuted sub-label after the primary label (e.g. "Card" under "dLocal").
filterRecord<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).