Weighted Metric Wall
A wall of KPI tiles whose sizes are computed automatically from the weight of each value, revealed largest-first in a cascade, expanding a detail layer on hover.
npx shadcn@latest add https://webberui.com/r/weighted-metric-wall.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<WeightedMetricWall />
Installation
npx shadcn@latest add https://webberui.com/r/weighted-metric-wall.jsonOr, once registries are configured in components.json, install it as @webberui/weighted-metric-wall.
Usage
import { WeightedMetricWall } from "@/components/ui/weighted-metric-wall";
<WeightedMetricWall
aria-label="Operating metrics this month"
metrics={[
{
label: "Monthly revenue",
value: "$128K",
weight: 100,
delta: 12.4,
deltaSuffix: "%",
detail: "Revenue hit a record high, mostly from annual plan renewals.",
},
{
label: "Active users",
value: "24,860",
weight: 70,
delta: 8.1,
deltaSuffix: "%",
},
{ label: "Conversion rate", value: "3.8%", weight: 40, delta: -0.6 },
]}
/>Each tile's column and row span is derived automatically from weight (when omitted, it tries to derive a number from value) relative to the maximum: the larger the value, the larger its footprint. On reveal the tiles surface in order from largest to smallest, and on hover or keyboard focus the detail layer expands over the tile.
Props
WeightedMetricWall
| Prop | Type | Default | Description |
|---|---|---|---|
metrics | WeightedMetric[] | — | Metric data; each tile's size is computed automatically from its weight |
columns | number | 4 | Number of grid columns (1–6) |
maxSpan | number | 2 | Maximum column / row span of a single tile (the footprint cap) |
rowHeight | number | 92 | Base height of each row (px) |
gap | number | 12 | Spacing between tiles (px) |
delay | number | 0 | Delay before the reveal starts (seconds) |
stagger | number | 0.08 | Interval between units as they surface from largest to smallest (seconds) |
once | boolean | true | Plays only the first time the element enters the viewport |
onMetricClick | (metric, index) => void | — | Tile click callback; when provided, tiles are presented with button semantics |
aria-label | string | — | Lets assistive technology identify what the whole wall is for |
WeightedMetric
| Field | Type | Description |
|---|---|---|
label | string | Metric name (the tile's main heading) |
value | ReactNode | The primary number; numbers get thousands separators automatically |
weight | number | The weight deciding the footprint size; derived from value when omitted |
detail | ReactNode | The detail content expanded on hover / focus |
delta | number | Relative change: positive = up, negative = down, 0 = flat |
deltaSuffix | string | Suffix shown after delta (such as "%" or "pt") |
unit | ReactNode | Secondary caption below the value |
id | string | Unique key; falls back to label as the key when omitted |
className | string | Overrides this tile's class (to add an accent background, say) |
How it works
- The size is derived as
ceil((weight / max weight) × maxSpan), clamped to1..min(maxSpan, columns); tiles use a square span. - The grid uses
grid-auto-flow: dense, so small tiles backfill the gaps left by large ones and the whole wall packs itself tightly. - The order in which tiles surface follows the weight ranking from largest to smallest, with
delayandstaggersetting the cascade rhythm — independently of DOM order. - If the wall sits inside a scroll container, the reveal animation is triggered by
whileInView, andoncedecides whether it replays after leaving the viewport.
Accessibility
- When the user has "reduce motion" enabled at the system level, the reveal and the detail layer transitions are disabled, so the tiles appear in their final state directly and the detail layer switches instantly.
- Every tile is keyboard focusable, and focus expands the detail layer just like hover; each tile carries an
aria-labelthat announces its name, value, and trend. - The trend arrow icons are marked
aria-hidden, and the detail overlay is a visual aid (aria-hidden), so nothing is announced twice alongside the tile label. - When
onMetricClickis provided, tiles are presented asrole="button"and can be triggered with Enter / Space.
Callout Stat Hero
A hero layout built around one giant number filling the first screen, with SVG leader lines drawn one by one as you scroll, connecting parts of the number to surrounding annotation cards that surface in sequence.
Year Pulse Grid
A GitHub-style wall of day cells for a whole year — a wave sweeps across on load lighting cells up by intensity, hovering a day floats an event card, and clicking a cell selects it and filters the linked sidebar list to that day's events.