Selectable KPI cell
A clickable KPI cell used inside multi-KPI hero blocks (Volume tab hero, Conversion rate tab, Fraud tab) where several cells sit side-by-side and the user picks which one the chart below reacts to. Single <button> that stacks a text-sm muted label (with optional Info icon prefix that mounts a Tooltip) + a text-3xl semibold value (with optional text-lg muted suffix) OR a text-sm 'No results' placeholder when noData is true. Active state (`active` prop) tints the whole cell primary (bg-primary/5 + text-primary on value / suffix / noData). Inactive uses hover:bg-muted/60 to hint the affordance. Optional `actions` slot renders top-right, revealed on hover / focus / when a nested DropdownMenu is open. DIFFERENT from KpiCard (standalone metric with own trend arrow + View more): SelectableKpiCell lives in a grid of siblings, one active at a time.
Anatomy
The outer wrapper is a group/kpi <div> with a relatively-positioned actions overlay. Inside it, a single <button> stacks (top-to-bottom): a text-sm muted label row with the optional Info icon prefix, then the value + suffix baseline row (or the 'No results' placeholder). The active variant paints bg-primary/5 on the button and text-primary across the value / suffix / noData; inactive uses hover:bg-muted/60 as the affordance hint. The actions overlay sits absolute in the top-right and opacity-0 fades in on group hover / focus / when a nested aria-expanded=true is present.
- 1Group wrapper
group/kpi <div>, relative. Owns the group-hover / group-focus context for the actions overlay reveal.
- 2Button surface
The full-width <button> that carries the click affordance. rounded-md p-5 with two active-state paints: bg-primary/5 (active) vs bg-transparent + hover:bg-muted/60 (inactive).
- 3Label row
flex items-center gap-1.5 text-sm muted. Renders the optional Info icon (size-4, weight light) with a Tooltip on hover, followed by the label text. Info click stops propagation so it doesn't activate the cell.
- 4Value + suffix
flex items-baseline gap-1. Value = text-3xl font-semibold tracking-tight (text-primary when active, text-foreground otherwise); suffix = text-lg font-normal (text-primary when active, text-muted-foreground otherwise).
- 5No results
Replaces the value row entirely when noData is true. text-sm text-primary when active, text-muted-foreground otherwise.
- 6Actions overlay
Absolute right-2 top-2. opacity-0 by default; reveals on group-hover, group-focus-within, and while a nested aria-expanded=true trigger is open (so a click that opens a DropdownMenu keeps the CTA visible). Wrap actions' onClick with e.stopPropagation() so they don't also activate the cell.
Recipes
The Volume-tab hero shape: 3 SelectableKpiCells side-by-side inside a Card, one active. Parent owns the `active` state and swaps it on click. A downstream chart reads `active` to decide which series to render.
const [active, setActive] = React.useState<KpiKey>("totalVolume");
<Card className="gap-0 overflow-hidden p-6">
<div className="grid grid-cols-1 gap-x-2 gap-y-2 sm:grid-cols-3">
{KPIS.map((k) => (
<SelectableKpiCell
key={k.key}
label={k.label}
info={k.info}
value={k.value}
suffix={k.suffix}
active={active === k.key}
onClick={() => setActive(k.key)}
/>
))}
</div>
</Card>A single cell used as a toggle. Uncommon in prod — SelectableKpiCell is designed for a grid — but useful for A/B demos or as a placeholder while the grid is being built.
<SelectableKpiCell
label="Total sales volume"
info="Sum of processed volume across all statuses."
value="$21.55M"
suffix="USD"
active={active}
onClick={() => setActive((v) => !v)}
/>Pass `noData` when the metric has no data in the current range. 'No results' takes the value slot; the label + Info tooltip still render. Combines with `active` (active + noData paints 'No results' in primary too).
<SelectableKpiCell
label="APM conversion rate"
info="Conversion for alternative payment methods."
value="—"
noData
/>Pass `actions` for a ghost icon Button that reveals on hover / focus / while its menu is open. Canonical: a DotsThree menu opening a per-cell DropdownMenu (add to dashboard, download, etc.). Always wrap the CTA's onClick with e.stopPropagation() so the cell doesn't also activate.
<SelectableKpiCell
label="Total sales volume"
value="$21.55M"
suffix="USD"
active={active}
onClick={() => setActive((v) => !v)}
actions={
<Button variant="ghost" size="icon" aria-label="More options"
onClick={(e) => e.stopPropagation()}
>
<DotsThree weight="light" />
</Button>
}
/>Import
Copy this import line at the top of the file where you compose this molecule.
import { SelectableKpiCell } from "@/components/patterns/selectable-kpi-cell";Props
Everything else is forwarded to the underlying elements via ...props.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string (required) | — | The metric label above the value. text-sm muted. |
| info | string | — | Tooltip text on an Info icon that prefixes the label. Omit to hide the icon (label goes solo). |
| value | string (required) | — | The KPI value. Rendered text-3xl semibold. |
| suffix | string | — | Optional suffix next to the value ("USD", "BRL", "%"). Rendered text-lg normal. |
| active | boolean | — | When true, the cell paints active: bg-primary/5 + text-primary-text on value / suffix / noData. Caller owns the active/inactive state across the grid. |
| onClick | () => void | — | Fires when the cell is clicked. Wire it to a parent state update that swaps `active` across sibling cells. |
| actions | ReactNode | — | Top-right slot revealed on hover / focus / when a nested DropdownMenu is open. Canonical: a ghost icon Button. Wrap onClick with e.stopPropagation() so the CTA doesn't also activate the cell. |
| noData | boolean | — | When true, renders "No results" in the value slot instead of the big number. Use when the metric has no data in the current range. |
| className | string | — | Extra classes on the outer <div>. |
Related
Cross-links to the atoms this molecule composes and sibling patterns.
- KPI cardThe standalone-metric sibling (with its own trend arrow + View more link). SelectableKpiCell is the multi-cell grid variant.
- Realtime status cardThe other KPI sibling — display-only with a trend Badge + subtitle footer, for real-time monitoring surfaces.
- Chart cardThe typical consumer of the `active` state — the chart below the KPI grid renders the series matching the active cell.