Patterns

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.

Updated Aug 6, 2026 by Leonardo Posada

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.

  1. 1
    Group wrapper

    group/kpi <div>, relative. Owns the group-hover / group-focus context for the actions overlay reveal.

  2. 2
    Button 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).

  3. 3
    Label 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.

  4. 4
    Value + 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).

  5. 5
    No results

    Replaces the value row entirely when noData is true. text-sm text-primary when active, text-muted-foreground otherwise.

  6. 6
    Actions 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

Grid (canonical — 3 cells + reactive chart)

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>
Standalone (toggle)

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)}
/>
No results

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
/>
With top-right actions slot

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.

SelectableKpiCell is a controlled cell — the caller owns `active`. Place several inside a Card grid and swap `active` across siblings on click; the downstream chart reads which one is active to render.
import { SelectableKpiCell } from "@/components/patterns/selectable-kpi-cell";

Props

Everything else is forwarded to the underlying elements via ...props.

PropTypeDefaultDescription
labelstring (required)The metric label above the value. text-sm muted.
infostringTooltip text on an Info icon that prefixes the label. Omit to hide the icon (label goes solo).
valuestring (required)The KPI value. Rendered text-3xl semibold.
suffixstringOptional suffix next to the value ("USD", "BRL", "%"). Rendered text-lg normal.
activebooleanWhen 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() => voidFires when the cell is clicked. Wire it to a parent state update that swaps `active` across sibling cells.
actionsReactNodeTop-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.
noDatabooleanWhen true, renders "No results" in the value slot instead of the big number. Use when the metric has no data in the current range.
classNamestringExtra 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.