Multi-page dashboard One page cannot hold it and the sections have different readers. Each page needs its own answer to is-it-okay at the top, because a reader who lands on page three never saw page one. npx shadcn@latest add tabs button dropdown-menu npm i lucide-react Tokens this needs: --card, --card-foreground, --muted, --muted-foreground, --border, --status-nominal, --status-warn, --status-critical The status, chart, scale, state and direction names are an extension, not a rename. shadcn has --destructive and five --chart-* and nothing else in this territory. ──────────────────────────────────────────────────────────────────────── // MultiPageDashboard.tsx ──────────────────────────────────────────────────────────────────────── import type { ReactNode } from "react"; import { ChevronDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; /** Closed. Every page answers is-it-okay in one of three words, so the * overview can put the answers side by side. */ export type Status = "nominal" | "warn" | "critical"; const STATUS = { nominal: "text-status-nominal", warn: "text-status-warn", critical: "text-status-critical" }; /** What every page receives. A page cannot hold its own range, which is how * two pages end up comparing different windows without anyone noticing. */ export type Shared = { range: string; scope: string }; export type Page = { id: string; title: string; status: Status; /** One line on whether it is okay. Printed at the top of the page and on * its overview tile, so a reader landing here never needs page one. */ headline: string; body?: (shared: Shared) => ReactNode; }; export const RANGES = ["1h", "6h", "24h", "7d"] as const; export function MultiPageDashboard({ subject, pages, shared, onShared, onScope, page, onNavigate }: { subject: string; /** Peers at one scope, differing in what they show. Levels of one subject * narrowing are a different pattern. */ pages: Page[]; shared: Shared; onShared: (next: Shared) => void; /** Opens the app's scope picker. */ onScope: () => void; /** "overview" or a page id. Lift it into the URL with the shared state, so * an alert can link to the right page with the scope intact. */ page: string; onNavigate: (id: string) => void; }) { const current = pages.find((p) => p.id === page); return (
{/* One header on every page: same title, same controls, same places. */}

{subject} · {shared.scope}

{RANGES.map((r) => onShared({ ...shared, range: r })}>last {r})}
Overview {pages.map((p) => ( {p.title} ))}
{current ? ( <>

{current.status} · {current.headline}

{current.body?.(shared)}
) : ( // The landing page. Its job is to say which of the others to open.
{pages.map((p) => ( ))}
)}
); } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { useState } from "react"; import { MultiPageDashboard, type Page, type Shared } from "./MultiPageDashboard"; const figure = (label: string, value: string) => (

{label}

{value}

); /** Four peer pages about one service. Every body prints the range it was * given, which is the only range there is. */ const PAGES: Page[] = [ { id: "traffic", title: "Traffic", status: "nominal", headline: "1,080 req/s, up 3% on the window before.", body: (s) =>
{figure(`requests, last ${s.range}`, "22.4M")}{figure("peak req/s", "1,112")}
}, { id: "errors", title: "Errors", status: "nominal", headline: "0.04%, within the 0.5% budget.", body: (s) =>
{figure(`5xx, last ${s.range}`, "8,960")}{figure("budget used", "8%")}
}, { id: "latency", title: "Latency", status: "warn", headline: "p95 at 312ms after a 24ms rise; the 400ms page threshold is close.", body: (s) =>
{figure(`p95, last ${s.range}`, "312ms")}{figure("p50", "88ms")}
}, { id: "cost", title: "Cost", status: "nominal", headline: "£1,420 for the window, on the forecast line.", body: (s) =>
{figure(`spend, last ${s.range}`, "£1,420")}{figure("forecast", "£1,400")}
}, ]; /** Lands on the overview. The range you pick in the header is the range on * every page, because there is only one. */ export default function Demo() { const [shared, setShared] = useState({ range: "6h", scope: "checkout" }); const [page, setPage] = useState("overview"); return ( {}} page={page} onNavigate={setPage} /> ); }