Drilldown Stat Grid
A grid of KPI tiles; clicking a tile expands a full-width detail panel below its row while the remaining tiles move down to make room, and clicking again collapses it.
npx shadcn@latest add https://webberui.com/r/drilldown-stat-grid.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<DrilldownStatGrid />
Installation
npx shadcn@latest add https://webberui.com/r/drilldown-stat-grid.jsonOr, once registries are configured in components.json, install it as @webberui/drilldown-stat-grid.
Usage
import {
DrilldownStatGrid,
type DrilldownStat,
} from "@/components/ui/drilldown-stat-grid";
const stats: DrilldownStat[] = [
{
id: "revenue",
label: "Revenue this month",
value: "$32.5k",
delta: 12.4,
trend: [18, 22, 20, 26, 24, 30, 33],
detail: <p>Revenue grew 12.4% over last month, driven mainly by subscription plan renewals.</p>,
},
// ...more KPIs
];
<DrilldownStatGrid stats={stats} columns={3} />;Controlled mode
Passing openId and onOpenChange switches to controlled mode, letting something outside (a URL parameter or another panel, say) decide which tile is expanded:
const [openId, setOpenId] = React.useState<string | null>("revenue");
<DrilldownStatGrid stats={stats} openId={openId} onOpenChange={setOpenId} />;Without openId it is uncontrolled and manages the state internally, and defaultOpenId can specify which item starts expanded.
Custom detail
renderDetail overrides stat.detail, which suits computing the drilldown content live for each tile:
<DrilldownStatGrid
stats={stats}
renderDetail={(stat) => <MyChart metric={stat.id} />}
/>;Props
DrilldownStatGrid
| Prop | Type | Default | Description |
|---|---|---|---|
stats | DrilldownStat[] | — | KPI tile data |
columns | number | 3 | Number of grid columns, deciding where the detail is inserted and how tiles make room |
openId | string | null | — | Controlled mode: the id of the currently expanded tile |
defaultOpenId | string | null | null | The initially expanded id in uncontrolled mode |
onOpenChange | (id: string | null) => void | — | Fires on expand / collapse |
renderDetail | (stat: DrilldownStat) => ReactNode | — | Custom detail content, overriding stat.detail |
duration | number | 0.32 | Expand / collapse duration (seconds) |
className | string | — | Appended to the grid container |
tileClassName | string | — | Appended to each tile button |
DrilldownStat
| Field | Type | Description |
|---|---|---|
id | string | Unique key, used to match the row that makes room and the controlled expansion — keep it stable |
label | ReactNode | KPI name (the small heading at the top left of the tile) |
value | ReactNode | The primary number (format it yourself) |
unit | ReactNode | The unit after the number (at a smaller size) |
delta | number | Percentage change; its sign decides the up/down colors and arrow |
deltaLabel | ReactNode | Overrides the change text; when omitted, +delta% is shown |
icon | ReactNode | Icon at the top right of the tile |
trend | number[] | Data points for the mini trend chart (drawn only with 2 or more points) |
detail | ReactNode | The full-width detail content shown once drilled down |
How it works
- The detail block fills the whole row with
grid-column: 1 / -1and is inserted after the last cell of the row containing the clicked tile, so the detail expands in the same place regardless of which cell in that row you click. - On expand, the height of the full-width row animates from
0toauto, and the tiles after it move down smoothly to make room as the layout grows; on collapse the space is released in reverse and the tiles settle back into place. columnsdecides both the CSS layout and the insertion row computed in JS, and the two must agree — which is why the column count is fixed rather thanauto-fill.
Accessibility
- Each tile is a native
<button>carryingaria-expandedandaria-controls; the detail block isrole="region"and points back at the triggering tile witharia-labelledby. - Keyboard operation is supported:
Enter/Spaceexpands or collapses, andEsccollapses and moves focus back to the original tile. - When the user has "reduce motion" enabled at the system level, expand / collapse switches instantly and the press scale is disabled, while the layout structure stays unchanged.
- The trend chart and the arrow icons are all marked
aria-hidden, so they do not interfere with assistive technology announcing the values.
Weather Widget
An animated weather card with sun-glow, raindrop, drifting-cloud, and snowflake scenes, day/night colors, and a week forecast
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.