Pattern gallery

Dashboard stat groups

Several headline counts read as one glanceable row, not a hunt across the page.

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.
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 somewhere
  • speedway/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.

@versable-git/ui · composites proven in the apps