Semantic grouping Panels that belong together by meaning, laid out so the grouping is visible without a box around everything. A heading and a gap do more work than a border, and cost less ink. npx shadcn@latest add separator card Tokens this needs: --card, --muted-foreground, --border, --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. ──────────────────────────────────────────────────────────────────────── // PanelGroups.tsx ──────────────────────────────────────────────────────────────────────── import type { ReactNode } from "react"; import { Separator } from "@/components/ui/separator"; import { cn } from "@/lib/utils"; /** One axis per page. Subsystem, signal type or audience, chosen once, so a * page cannot group by service at the top and by signal further down. */ export type Axis = "subsystem" | "signal" | "audience"; export type Group = { /** Specific. "Misc", "Other" and "Metrics" are confessions, and they are * flagged rather than silently accepted. */ label: string; /** Ordered by importance, never by when they were added. */ panels: ReactNode[]; }; type Props = { axis: Axis; groups: Group[]; columns?: 2 | 3 | 4; }; const SAYS_NOTHING = /^(misc|miscellaneous|other|metrics|general|stuff)$/i; const COLS = { 2: "grid-cols-2", 3: "grid-cols-3", 4: "grid-cols-4" }; export function PanelGroups({ axis, groups, columns = 3 }: Props) { return (
{groups.map((g) => { const empty = SAYS_NOTHING.test(g.label.trim()); return ( // A heading and a gap do the work. No box around everything, and // the boundary is visible without colour.

{g.label}

{g.panels.length > 0 ? (
{g.panels}
) : (

Nothing here yet.

)}
); })}
); } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { Card } from "@/components/ui/card"; import { cn } from "@/lib/utils"; import { PanelGroups } from "./PanelGroups"; /** * Nine panels grouped by signal type. Traffic, errors, saturation, in that * order, because that is the order a person diagnosing reads them. One error * panel is firing and sits where a reader would look for it. */ const Stat = ({ label, value, critical }: { label: string; value: string; critical?: boolean }) => (

{label}

{value}

); export default function Demo() { return (
, , , ], }, { label: "Errors", panels: [ , , , ], }, { label: "Saturation", panels: [ , , , ], }, ]} />
); }