Pattern gallery

Route-matched boot skeletons

One dispatcher, one skeleton per route: the first paint already looks like the page you asked for.

What this solves

Visual and information. A generic spinner tells the reader nothing about where they landed. Dispatching the boot frame on the URL lets the destination's own icon, title, and static copy paint on the very first frame, so the app already looks like the page asked for before a single byte of real data has come back.

Use it when
  • Every top-level route during the auth-boot windowwalmart-mvp's routeBootSkeleton(pathname), the dispatcher every route resolves through before its data loads
  • A record route whose header facts are already known from the URLspeedway's job detail route, where the back link and the file-count subtitle render before the record itself arrives

Rendered

Fixture data; check both themes.

Boot frame for /jobs

Jobs

Every batch you've imported and exactly where it is in the pipeline. Click a row for the stage-by-stage view.
JobModuleOwnerStatusStarted
patterns/boot-skeletons/example.tsxCopy this into a fresh route and it renders as above.
"use client";
import { Button, Card, PageTitle, TableSkeleton } from "@versable-git/ui";
import type { TableColumn } from "@versable-git/ui";
import { useState } from "react";
import { RecordSkeleton } from "./example-record";
import { SettingsSkeleton } from "./example-settings";
// Route-matched boot skeletons: the boot frame dispatches on pathname so the
// first paint mirrors the destination's real layout, never a generic box.
// The list route is the primary variant, kept inline; the record and
// settings routes live beside this file since their layouts differ in code,
// not just in props.
const jobsColumns: TableColumn<Record<string, never>>[] = [
{ key: "id", header: "Job" },
{ key: "module", header: "Module" },
{ key: "owner", header: "Owner" },
{ key: "status", header: "Status" },
{ key: "started", header: "Started" },
];
function ListSkeleton() {
return (
<div className="flex flex-col gap-4">
<PageTitle
Icon="Table"
title="Jobs"
subtitle="Every batch you've imported and exactly where it is in the pipeline. Click a row for the stage-by-stage view."
/>
<TableSkeleton columns={jobsColumns} rows={4} />
</div>
);
}
const ROUTES = [
{ path: "/jobs", label: "/jobs (list)", Frame: ListSkeleton },
{ path: "/jobs/job_9421", label: "/jobs/job_9421 (record)", Frame: RecordSkeleton },
{ path: "/settings", label: "/settings (settings)", Frame: SettingsSkeleton },
];
export function BootSkeletons() {
const [path, setPath] = useState(ROUTES[0]!.path);
const route = ROUTES.find((r) => r.path === path) ?? ROUTES[0]!;
return (
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-2">
{ROUTES.map((r) => (
<Button
key={r.path}
size="sm"
variant={r.path === path ? "solid" : "outline"}
content={`Boot on ${r.label}`}
onClick={() => setPath(r.path)}
/>
))}
</div>
<Card title={`Boot frame for ${route.path}`} noAnimate>
<route.Frame />
</Card>
</div>
);
}

Where it ships

  • walmart-mvp/frontend/src/lib/routeSkeletons.tsx:24-35routeBootSkeleton(pathname): every top-level route maps to a skeleton mirroring that page's real static layout

App-specific: The real map covers every top-level route with a hand-built skeleton per page, and the dispatcher runs during the auth boot only; once data loads, the page's own Suspense fallbacks take over.

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