Provider legend row
Horizontally scrollable pill row used above provider-heavy charts (Approval rate by provider, Error rate, Latency p90/p95/p99, Timeout rate). Each pill stacks [dot + name muted] then [value 2xl semibold tabular]. When onSelectKey is wired, pills become buttons — clicking one toggles it selected; other pills dim to opacity-40 so the chart consumer can filter to a single provider. Click the same pill again to clear.
Anatomy
A horizontally scrollable row of pills. The outer container bleeds -mx-6 px-6 so the first/last pill isn't clipped by the ChartCard's p-6. Each pill has two rows: first = dot + name (text-sm muted); second = value (text-2xl semibold tabular-nums foreground). min-w-28 (112px) per pill so widths stay consistent.
- 1Scroll container
-mx-6 overflow-x-auto px-6 pb-2. Lets 6+ providers scroll horizontally inside a ChartCard without clipping.
- 2Pill (static)
min-w-28 flex-col gap-1 px-2 py-1. Used when onSelectKey is not defined. Pure <div>.
- 3Pill (interactive)
Same layout but rendered as a <button> with aria-pressed. Hover = bg-accent. Selected = bg-muted. When any pill is selected, unselected pills get opacity-40.
- 4Dot
size-2 rounded-full. Color from provider.color — prefer var(--chart-N).
- 5Name
text-sm text-muted-foreground. Truncates with min-w-0 sibling.
- 6Value
text-2xl font-semibold tabular-nums text-foreground. Current KPI (%, ms).
Recipes
Omit onSelectKey. Pills render as <div>s with no hover / click behavior. Use when the chart doesn't support filter-to-one-provider or when the row is purely informational.
<ProviderLegendRow providers={providers} />
// providers: ProviderLegendItem[] = [
// { key, name, color, value },
// ]Wire selectedKey + onSelectKey to enable click-to-filter. The chart consumer reads selectedKey and filters its series to the matching provider (or renders all when null). Selection dims non-selected pills to opacity-40.
const [selected, setSelected] = React.useState<string | null>(null);
<ProviderLegendRow
providers={providers}
selectedKey={selected}
onSelectKey={setSelected}
/>
// Read `selected` in the sibling chart to filter its series.Import
Copy this import line at the top of the file where you compose this atom.
import { ProviderLegendRow, type ProviderLegendItem } from "@/components/ui/provider-legend-row";Props
Everything else from the underlying HTML or Radix primitive is forwarded via ...props.
ProviderLegendRow
| Prop | Type | Default | Description |
|---|---|---|---|
| providers | ProviderLegendItem[] (required) | — | The pills to render. See item shape below. |
| selectedKey | string | null | — | Selected provider key. Pass null for no selection. When defined together with `onSelectKey`, pills become interactive buttons. |
| onSelectKey | (key: string | null) => void | — | Fires when the user toggles a pill. Receives the new key, or null to clear. Absent = pills stay static. |
| className | string | — | Extra classes on the outer overflow-x-auto wrapper. |
ProviderLegendItem
| Prop | Type | Default | Description |
|---|---|---|---|
| key | string (required) | — | Stable key. Used for React reconciliation AND for `selectedKey` matching. |
| name | string (required) | — | Provider name (dot + name row, muted). |
| color | string (required) | — | Dot color. Prefer var(--chart-N). |
| value | string (required) | — | Current KPI ("89.13%", "1,287 ms"). Rendered text-2xl semibold tabular-nums. |
Related
Cross-links to atoms and patterns you may reach for next.
- Status legendStatic wrap-friendly sibling. Use when the legend should stack instead of scroll and interactivity isn't needed.
- Chart cardCanonical host — sits at the top of the ChartCard body above the chart.
- ChartThe chart consumer reads selectedKey and filters its series accordingly.