Dashboard stat groups
What this solves
Visual and information: tone-colored icons let the eye sort good, bad, and neutral numbers before reading a single label, and a fixed row of headline counts puts the facts a reader needs first in one place, instead of scattered across cards further down the page.
Use it when
- A workspace dashboard's opening rowspeedway workspace.tsx: Assigned to you, Jobs in progress, Ready to export, Onboarded this month
- Landing on a home page with an account to summarizewalmart-mvp Home.tsx: the 4-up KPI row, each tile with tone, href, and arrow
- A single record's own summary row, not a dashboardspeedway job run.tsx: Examined, Sent to review, Duration for one run
Rendered
Fixture data; check both themes.Active products
12,480
live in the catalog
Processing now
324
across 3 running jobs
Errors to resolve
17
waiting in the queue
Onboarded this month
1,908
parts onboarded
patterns/dashboard-stats/example.tsxCopy this into a fresh route and it renders as above.
"use client";import { StatTile, Tabs, type IconKeys } from "@versable-git/ui";import { useState } from "react";// A dashboard's opening row: one tile per headline number, in an auto-fit// grid that reflows without breakpoint tuning. Tiles that lead somewhere// carry href and arrow; purely informational tiles omit both. The Tabs knob// switches the same four tiles between their three shipped states: plain// values, values with a trend line, and the loading placeholder that keeps// every label and icon visible while only the number is still arriving.type View = "values" | "trend" | "loading";interface Stat { Icon: IconKeys; tone?: string; label: string; value: string; hint: string; href?: string; arrow?: boolean; trend?: "up" | "down" | "flat"; trendLabel?: string;}const base: Stat[] = [ { Icon: "Success", tone: "text-success", label: "Active products", value: "12,480", hint: "live in the catalog", href: "#active", arrow: true, trend: "up", trendLabel: "+4% this week", }, { Icon: "Refresh", tone: "text-accent", label: "Processing now", value: "324", hint: "across 3 running jobs", href: "#jobs", arrow: true, trend: "flat", trendLabel: "steady", }, { Icon: "Warning", tone: "text-warning", label: "Errors to resolve", value: "17", hint: "waiting in the queue", href: "#errors", arrow: true, trend: "down", trendLabel: "-6 since yesterday", }, { Icon: "Database", label: "Onboarded this month", value: "1,908", hint: "parts onboarded", },];export function DashboardStats() { const [view, setView] = useState<View>("values"); return ( <div className="flex flex-col gap-4"> <Tabs size="sm" value={view} onChange={(v) => setView(v as View)} items={[ { value: "values", label: "Values" }, { value: "trend", label: "With trend" }, { value: "loading", label: "Loading" }, ]} /> <div className="grid grid-cols-[repeat(auto-fit,minmax(12.5rem,1fr))] gap-4"> {base.map((s) => { if (view === "loading") { return <StatTile key={s.label} Icon={s.Icon} tone={s.tone} label={s.label} value="" hint={s.hint} loading />; } if (view === "trend") { return <StatTile key={s.label} {...s} />; } const { trend: _trend, trendLabel: _trendLabel, ...rest } = s; return <StatTile key={s.label} {...rest} />; })} </div> </div> );}Where it ships
walmart-mvp/frontend/src/pages/Home.tsx:176-217the 4-up opening row: tone, href, and arrow on every tile that leads somewherespeedway/app/routes/workspaces/workspace.tsx:300-345the auto-fit minmax grid; tiles deep-link through the router with linkAs and carry a go-to tooltip
App-specific: Real pages derive the values in their loaders, wire href through the app router with linkAs, and source tooltip content from the same nav meta the sidebar reads, so tile and sidebar cannot drift.