Skip to content

Record page

One record: its name, a sticky line tab bar, activity on the left and a 21rem attribute rail.

What it's for#

A record page is one deal, company or person: who and what it is, where it stands, what happened, and every attribute you can change.

It is a document page that splits in two from 1024px. The left column reads top to bottom, from the record's name through its stage to tabs of overview and activity. The right column is a 21rem rail of attributes that edit in place. People arrive from a list page or a search and move to the next record with J and K.

Routes built on the record page
RouteRecord
/deals/d_northwindA deal: stage track with exit criteria, line items, buying committee, files and notes.
/companies/c_northwindA company: overview, activity, people, deals and notes, with firmographics in the rail.
/people/p_danaA person: overview, activity, emails and notes, with contact details in the rail.

Live composition#

The Northwind Freight deal at 1280px. Click a stage, tick the exit criteria to fill the move button, switch tabs, click an amount to edit it, or collapse a rail section.

Invoice automation rollout

Northwind Freight$186,000.00Closes Oct 2, 2026
NegotiationOn trackCommit

Next step

Walk Jordan through the 3-year option

Wednesday, Sep 30 at 2:00 PM, before Friday's review with their CFO.

Line items

ProductQuantityAnnual
Invoice capture22,000 a month$118,000.00
Vendor portal1,400 suppliers$44,000.00
Payment runs9 entities$24,000.00
import { Button } from "@oration/canon/components/button";import { Checkbox } from "@oration/canon/components/checkbox";import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@oration/canon/components/collapsible";import { EmptyState } from "@oration/canon/components/data-state";import { EditableText } from "@oration/canon/components/editable-text";import { MonogramTile } from "@oration/canon/components/monogram-tile";import { StatusLabel } from "@oration/canon/components/status-dot";import { Stepper } from "@oration/canon/components/stepper";import { Tabs, TabsContent, TabsList, TabsTrigger } from "@oration/canon/components/tabs";import { Tag } from "@oration/canon/components/tag";import {  Timeline,  TimelineBody,  TimelineHeader,  TimelineItem,  TimelineTime,} from "@oration/canon/components/timeline";import { toast } from "@oration/canon/components/toast";import {  ArrowRightIcon,  CalendarIcon,  ChevronDownIcon,  ChevronRightIcon,  ChevronUpIcon,  CircleDollarSignIcon,  GaugeIcon,  HeartPulseIcon,  MailIcon,  MoreHorizontalIcon,  PanelRightIcon,  PhoneIcon,  SparklesIcon,  TrendingUpIcon,  UserIcon,  XIcon,} from "lucide-react";import * as React from "react";import { AppShell } from "../document-page/frame";export function DealRecord() {    const steps = [        { id: "discovery", label: "Discovery" },        { id: "proposal", label: "Proposal" },        { id: "negotiation", label: "Negotiation" },        { id: "won", label: "Closed won" },    ];    const [stage, setStage] = React.useState("negotiation");    const [exitDone, setExitDone] = React.useState(false);    const [amount, setAmount] = React.useState("$186,000.00");    const [close, setClose] = React.useState("Oct 2, 2026");    const [details, setDetails] = React.useState(false);    const current = steps.find((step) => step.id === stage);    const nextStep = steps[steps.findIndex((step) => step.id === stage) + 1];    const changeStage = (id: string) => {        const previous = stage;        setStage(id);        setExitDone(false);        toast.add({            title: `Moved to ${steps.find((step) => step.id === id)?.label}`,            description: "Tally logged the change on the deal.",            actionProps: {                children: "Undo",                onClick: () => setStage(previous),            },        });    };    const onKeyDown = (event: React.KeyboardEvent) => {        const target = event.target as HTMLElement;        if (target.closest("input, textarea, [role=tab]")) return;        if (event.metaKey || event.ctrlKey || event.altKey) return;        if (event.key === "j" || event.key === "k") {            event.preventDefault();            toast.add({                title: event.key === "j" ? "Next deal" : "Previous deal",                description:                    event.key === "j"                        ? "Vendor portal expansion, Halcyon"                        : "Multi-entity AP, Orchard Street",            });        }    };    const row = (        icon: React.ReactNode,        label: string,        value: React.ReactNode,    ) => (        <div            key={label}            className="grid min-h-8 grid-cols-[7.25rem_minmax(0,1fr)] items-center gap-1 text-13"        >            <dt className="flex min-w-0 items-center gap-2 px-1.5 text-muted-foreground [&>svg]:size-3.5 [&>svg]:shrink-0">                {icon}                <span className="truncate">{label}</span>            </dt>            <dd className="flex min-w-0 items-center">{value}</dd>        </div>    );    const plain = (value: React.ReactNode) => (        <span className="flex min-w-0 items-center gap-1.5 truncate px-1.5 py-1">            {value}        </span>    );    const section = (title: string, children: React.ReactNode) => (        <Collapsible            key={title}            defaultOpen            className="border-b border-border px-3 py-3 last:border-b-0"        >            <h2>                <CollapsibleTrigger className="group flex h-7 w-full items-center gap-1 rounded-md px-1.5 text-13 font-medium outline-none hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/40">                    <ChevronRightIcon                        aria-hidden="true"                        className="size-3.5 text-muted-foreground transition-transform duration-150 group-data-[panel-open]:rotate-90"                    />                    {title}                </CollapsibleTrigger>            </h2>            <CollapsibleContent>                <dl className="flex flex-col pt-1">{children}</dl>            </CollapsibleContent>        </Collapsible>    );    const attributes = (        <>            {section(                "Deal",                <>                    {row(                        <CircleDollarSignIcon />,                        "Amount",                        <EditableText                            label="Amount"                            value={amount}                            onCommit={setAmount}                            className="h-7 tabular-nums"                        />,                    )}                    {row(                        <TrendingUpIcon />,                        "Stage",                        plain(                            <Tag color="amber" dot>                                {current?.label}                            </Tag>,                        ),                    )}                    {row(                        <CalendarIcon />,                        "Close date",                        <EditableText                            label="Close date"                            value={close}                            onCommit={setClose}                            className="h-7 tabular-nums"                        />,                    )}                    {row(<UserIcon />, "Owner", plain("Maya Okafor"))}                    {row(                        <GaugeIcon />,                        "Forecast",                        plain(<Tag color="indigo">Commit</Tag>),                    )}                    {row(                        <GaugeIcon />,                        "Probability",                        plain(<span className="tabular-nums">70%</span>),                    )}                    {row(                        <HeartPulseIcon />,                        "Health",                        plain(                            <StatusLabel tone="success">On track</StatusLabel>,                        ),                    )}                    {row(                        <CalendarIcon />,                        "In stage",                        plain(<span className="tabular-nums">12 days</span>),                    )}                </>,            )}            {section(                "Company",                <>                    <div className="mb-1 flex items-center gap-2.5 rounded-lg px-1.5 py-1.5 hover:bg-muted">                        <MonogramTile                            name="Northwind Freight"                            color="teal"                            size="lg"                        />                        <span className="flex min-w-0 flex-col">                            <span className="truncate text-13 font-medium">                                Northwind Freight                            </span>                            <span className="text-xs text-muted-foreground">                                northwindfreight.com                            </span>                        </span>                    </div>                    {row(                        <TrendingUpIcon />,                        "Lifecycle",                        plain(<Tag color="blue">Opportunity</Tag>),                    )}                    {row(<GaugeIcon />, "ICP fit", plain("Strong"))}                    {row(                        <UserIcon />,                        "Invoices a month",                        plain(<span className="tabular-nums">22,000</span>),                    )}                </>,            )}            {section(                "Products",                <div className="flex flex-wrap gap-1.5 px-1.5 py-1">                    <Tag>Invoice capture</Tag>                    <Tag>Vendor portal</Tag>                    <Tag>Payment runs</Tag>                </div>,            )}        </>    );    return (        <AppShell            active="Deals"            crumbs={[                {                    label: "Deals",                    icon: <CircleDollarSignIcon className="size-3.5" />,                },                { label: "Invoice automation rollout" },            ]}            actions={                <>                    <span className="flex items-center">                        <Button                            variant="ghost"                            size="icon-sm"                            aria-label="Previous deal"                            onClick={() =>                                toast.add({ title: "Previous deal" })                            }                        >                            <ChevronUpIcon aria-hidden="true" />                        </Button>                        <Button                            variant="ghost"                            size="icon-sm"                            aria-label="Next deal"                            onClick={() => toast.add({ title: "Next deal" })}                        >                            <ChevronDownIcon aria-hidden="true" />                        </Button>                    </span>                    <Button                        variant="ghost"                        size="icon-sm"                        aria-label="More actions"                    >                        <MoreHorizontalIcon aria-hidden="true" />                    </Button>                    <Button                        variant="ghost"                        size="icon-sm"                        aria-label="Show details"                        className="@min-[1024px]:hidden"                        onClick={() => setDetails(true)}                    >                        <PanelRightIcon aria-hidden="true" />                    </Button>                </>            }        >            <section                aria-label="Invoice automation rollout"                className="relative flex min-h-0 flex-1"                onKeyDown={onKeyDown}            >                <div className="min-w-0 flex-1 overflow-y-auto">                    <div className="flex flex-col gap-4 px-6 pt-6 pb-4 @min-[640px]:px-8">                        <div className="flex items-start gap-4">                            <MonogramTile                                name="Northwind Freight"                                color="teal"                                size="xl"                            />                            <div className="min-w-0 flex-1">                                <h1 className="text-xl font-semibold tracking-[-0.015em]">                                    Invoice automation rollout                                </h1>                                <div className="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground">                                    <span className="font-medium text-foreground">                                        Northwind Freight                                    </span>                                    <span className="tabular-nums">                                        {amount}                                    </span>                                    <span className="tabular-nums">                                        Closes {close}                                    </span>                                </div>                                <div className="mt-2.5 flex flex-wrap items-center gap-1.5">                                    <Tag color="amber" dot>                                        {current?.label}                                    </Tag>                                    <StatusLabel tone="success">                                        On track                                    </StatusLabel>                                    <Tag color="indigo">Commit</Tag>                                </div>                            </div>                        </div>                        <div className="flex flex-wrap items-center gap-2">                            <Button variant="outline" size="sm">                                <MailIcon                                    data-icon="inline-start"                                    aria-hidden="true"                                />                                Email contacts                            </Button>                            <Button variant="outline" size="sm">                                <PhoneIcon                                    data-icon="inline-start"                                    aria-hidden="true"                                />                                Log activity                            </Button>                            <Button variant="ghost" size="sm">                                <SparklesIcon                                    data-icon="inline-start"                                    aria-hidden="true"                                />                                Ask an agent                            </Button>                        </div>                    </div>                    <div className="flex flex-col gap-3 px-6 pb-5 @min-[640px]:px-8">                        <Stepper                            steps={steps}                            current={stage}                            label="Deal stage"                            onStepSelect={changeStage}                        />                        {nextStep ? (                            <div className="flex flex-wrap items-center justify-between gap-3 rounded-xl bg-surface px-3.5 py-2.5">                                <label className="flex min-w-0 items-center gap-2.5 text-13">                                    <Checkbox                                        checked={exitDone}                                        onCheckedChange={setExitDone}                                    />                                    <span className="text-muted-foreground">                                        To leave {current?.label}:                                    </span>                                    <span className="font-medium">                                        Redlines returned and signer named                                    </span>                                </label>                                <Button                                    variant={exitDone ? "default" : "outline"}                                    size="sm"                                    onClick={() => changeStage(nextStep.id)}                                >                                    Move to {nextStep.label}                                    <ArrowRightIcon                                        data-icon="inline-end"                                        aria-hidden="true"                                    />                                </Button>                            </div>                        ) : (                            <p className="rounded-xl bg-surface px-3.5 py-2.5 text-13">                                Closed won on Sep 28.                            </p>                        )}                    </div>                    <Tabs defaultValue="overview" className="gap-0">                        <div className="sticky top-0 z-10 border-b border-border bg-background px-6 @min-[640px]:px-8">                            <TabsList                                variant="line"                                scrollable                                className="h-10 gap-3"                            >                                <TabsTrigger value="overview" className="px-0">                                    Overview                                </TabsTrigger>                                <TabsTrigger value="activity" className="px-0">                                    Activity                                </TabsTrigger>                                <TabsTrigger value="committee" className="px-0">                                    Buying committee{" "}                                    <span className="text-xs font-normal text-muted-foreground tabular-nums">                                        3                                    </span>                                </TabsTrigger>                                <TabsTrigger value="files" className="px-0">                                    Files                                </TabsTrigger>                                <TabsTrigger value="notes" className="px-0">                                    Notes                                </TabsTrigger>                            </TabsList>                        </div>                        <TabsContent                            value="overview"                            className="px-6 py-6 @min-[640px]:px-8"                        >                            <div className="flex max-w-3xl flex-col gap-8">                                <section className="flex flex-col gap-3">                                    <h2 className="flex min-h-7 items-center text-sm font-semibold">                                        Next step                                    </h2>                                    <div className="flex flex-col gap-1 rounded-xl bg-card p-4 shadow-border">                                        <p className="text-13 font-medium">                                            Walk Jordan through the 3-year                                            option                                        </p>                                        <p className="text-13 text-muted-foreground">                                            Wednesday, Sep 30 at 2:00 PM, before                                            Friday's review with their CFO.                                        </p>                                    </div>                                </section>                                <section className="flex flex-col gap-3">                                    <h2 className="flex min-h-7 items-center text-sm font-semibold">                                        Line items                                    </h2>                                    <div className="overflow-hidden rounded-xl bg-card shadow-border">                                        <table className="w-full text-13">                                            <thead>                                                <tr className="border-b border-border text-xs text-muted-foreground">                                                    <th                                                        scope="col"                                                        className="h-8 px-4 text-left font-medium"                                                    >                                                        Product                                                    </th>                                                    <th                                                        scope="col"                                                        className="px-3 text-right font-medium"                                                    >                                                        Quantity                                                    </th>                                                    <th                                                        scope="col"                                                        className="px-4 text-right font-medium"                                                    >                                                        Annual                                                    </th>                                                </tr>                                            </thead>                                            <tbody>                                                {[                                                    [                                                        "Invoice capture",                                                        "22,000 a month",                                                        "$118,000.00",                                                    ],                                                    [                                                        "Vendor portal",                                                        "1,400 suppliers",                                                        "$44,000.00",                                                    ],                                                    [                                                        "Payment runs",                                                        "9 entities",                                                        "$24,000.00",                                                    ],                                                ].map(                                                    ([                                                        product,                                                        quantity,                                                        annual,                                                    ]) => (                                                        <tr                                                            key={product}                                                            className="border-b border-border last:border-b-0"                                                        >                                                            <td className="h-9 px-4">                                                                {product}                                                            </td>                                                            <td className="px-3 text-right text-muted-foreground tabular-nums">                                                                {quantity}                                                            </td>                                                            <td className="px-4 text-right tabular-nums">                                                                {annual}                                                            </td>                                                        </tr>                                                    ),                                                )}                                            </tbody>                                        </table>                                    </div>                                </section>                            </div>                        </TabsContent>                        <TabsContent                            value="activity"                            className="px-6 py-6 @min-[640px]:px-8"                        >                            <Timeline className="max-w-3xl">                                <TimelineItem                                    icon={<MailIcon />}                                    tone="primary"                                >                                    <TimelineHeader>                                        <strong>Quill</strong>                                        <span>                                            drafted a reply to Jordan Lee                                        </span>                                        <TimelineTime>                                            Today, 7:06 AM                                        </TimelineTime>                                    </TimelineHeader>                                    <TimelineBody className="text-muted-foreground">                                        Waiting for Maya's approval on Home.                                    </TimelineBody>                                </TimelineItem>                                <TimelineItem                                    icon={<TrendingUpIcon />}                                    tone="success"                                >                                    <TimelineHeader>                                        <strong>Maya Okafor</strong>                                        <span>                                            moved the deal to Negotiation                                        </span>                                        <TimelineTime>                                            Sep 16, 3:40 PM                                        </TimelineTime>                                    </TimelineHeader>                                </TimelineItem>                                <TimelineItem icon={<PhoneIcon />}>                                    <TimelineHeader>                                        <strong>Priya Raman</strong>                                        <span>                                            logged a call with Jordan Lee                                        </span>                                        <TimelineTime>                                            Sep 12, 11:05 AM                                        </TimelineTime>                                    </TimelineHeader>                                </TimelineItem>                            </Timeline>                        </TabsContent>                        <TabsContent                            value="committee"                            className="px-6 py-6 @min-[640px]:px-8"                        >                            <ul className="flex max-w-3xl flex-col rounded-xl bg-card shadow-border">                                {[                                    ["Jordan Lee", "AP manager", "Champion"],                                    [                                        "Tomás Ferreira",                                        "Controller",                                        "Evaluator",                                    ],                                    ["Aisha Bello", "CFO", "Signer"],                                ].map(([name, title, role]) => (                                    <li                                        key={name}                                        className="flex items-center gap-3 border-b border-border px-4 py-2.5 text-13 last:border-b-0"                                    >                                        <span className="font-medium">                                            {name}                                        </span>                                        <span className="text-muted-foreground">                                            {title}                                        </span>                                        <Tag className="ml-auto">{role}</Tag>                                    </li>                                ))}                            </ul>                        </TabsContent>                        <TabsContent                            value="files"                            className="px-6 py-6 @min-[640px]:px-8"                        >                            <EmptyState                                size="sm"                                illustration="inbox"                                title="No files yet"                                description="Order forms and security packets attached to emails land here."                            />                        </TabsContent>                        <TabsContent                            value="notes"                            className="px-6 py-6 @min-[640px]:px-8"                        >                            <EmptyState                                size="sm"                                illustration="knowledge"                                title="No notes yet"                                description="Notes you write here are shared with everyone on the deal."                            />                        </TabsContent>                    </Tabs>                </div>                <aside                    aria-label="Invoice automation rollout details"                    className="hidden w-[21rem] shrink-0 overflow-y-auto border-l border-border bg-background @min-[1024px]:block"                >                    {attributes}                </aside>                {details ? (                    <div className="absolute inset-0 z-40 @min-[1024px]:hidden">                        <button                            type="button"                            aria-label="Close details"                            className="absolute inset-0 bg-black/25"                            onClick={() => setDetails(false)}                        />                        <div                            role="dialog"                            aria-label="Invoice automation rollout details"                            className="absolute inset-y-0 right-0 flex w-[22rem] max-w-[90%] flex-col bg-popover shadow-lg"                        >                            <div className="flex items-center gap-2 border-b border-border px-4 py-3">                                <p className="min-w-0 flex-1 truncate text-base leading-none font-medium">                                    Invoice automation rollout details                                </p>                                <Button                                    variant="ghost"                                    size="icon-sm"                                    aria-label="Close"                                    onClick={() => setDetails(false)}                                >                                    <XIcon aria-hidden="true" />                                </Button>                            </div>                            <div className="min-h-0 flex-1 overflow-y-auto">                                {attributes}                            </div>                        </div>                    </div>                ) : null}            </section>        </AppShell>    );}

Anatomy#

Seven parts. One through six scroll together in the record column; the rail scrolls on its own.

  1. App header. Crumbs end in the record's name with a switcher. Actions are the previous and next pager, favorite, a more menu, and the details button below 1024px. No filled button here.
  2. Record hero. A 48px Monogram tile or avatar, the record name as the page's one h1 in Headline, a subtitle line and meta tags.
  3. Record actions. Outline and ghost buttons for the record's common jobs: email, log activity, ask an agent.
  4. Stage track. Stepper: 4px segments, Ink 25 done, Ink 80 current, Well Gray upcoming. Under it, the exit criteria; the move button fills only once they are met.
  5. Line tab bar. Tabs in the line variant, 40px, sticky at the top of the record column so it rides under the header. It scrolls and fades its clipped edge.
  6. Tab content. Sections 32px apart, capped at 48rem. Overview leads with the next step; Activity is a Timeline.
  7. Attribute rail. 21rem with a hairline left edge, never a shadow. Collapsible sections of 32px label-left rows; values edit in place with Editable text and selects.

Measurements#

From DESIGN.md's Layout, Navigation and Meters sections, and RecordShell.

Record page measurements
PartValueClasses
Column100svh, two scrollersflex h-svh min-h-0 flex-col
Record gutters24px, 32px from 640pxpx-6 sm:px-8
Hero media48px, 12px cornersMonogramTile size="xl"
Record name20px Headlinetext-xl font-semibold tracking-[-0.015em]
Stage segments4px tall, 4px apartStepper
Tab bar40px, stickysticky top-0 z-10, h-10 gap-3
Tab content48rem maximummax-w-3xl, sections gap-8
Attribute rail21rem from 1024pxhidden w-[21rem] border-l lg:block
Rail rows32px minimum, 7.25rem labelsgrid min-h-8 grid-cols-[7.25rem_minmax(0,1fr)]
Details sheet22rem, 90vw at mostw-[22rem] max-w-[90vw]

Responsive behavior#

The rail is the only part that moves. Below 1024px it becomes a right sheet behind the header's details button.

Invoice automation rollout

Northwind Freight$186,000.00Closes Oct 2, 2026
NegotiationOn trackCommit

Next step

Walk Jordan through the 3-year option

Wednesday, Sep 30 at 2:00 PM, before Friday's review with their CFO.

Line items

ProductQuantityAnnual
Invoice capture22,000 a month$118,000.00
Vendor portal1,400 suppliers$44,000.00
Payment runs9 entities$24,000.00

From 640px gutters are 32px and every stage label shows. The attribute rail is still a sheet: press the details button in the header.

Record page at each width
WidthWhat changes
390px24px gutters. The stage track shows only the current label. Tabs scroll with a faded edge. Attributes open in a sheet.
768px32px gutters from 640px; every stage label shows. Attributes are still a sheet.
1024pxThe 21rem rail sits beside the record and the details button hides.
1440pxThe record column widens; tab content stays at 48rem.

States#

A record page has one page-level state, missing, and the rest belong to its parts.

States by region
RegionStates it owns
PageMissing: an Empty centered in the viewport, This deal isn't available, with Back to deals.
Stage trackDone, current and upcoming segments; clickable when the stage can change. Won and lost replace the exit row with a line of outcome.
Exit criteriaUnmet keeps the move button outline; met fills it.
TabsEach panel owns loading and empty. Counts trail the label in 12px Slate Meta.
Attribute rowsRead, editing, and empty as Empty in Faint Slate. Read-only values are plain text with no hover.

The line tab bar

Seven tabs in a narrow column. The list scrolls sideways and fades its clipped edge, per the Scroll Edge Rule.

Overview for Invoice automation rollout. Scroll the tabs sideways; the clipped edge fades over 2rem. With a tab focused, → moves to the next one.
import { Kbd } from "@oration/canon/components/kbd";import { Tabs, TabsContent, TabsList, TabsTrigger } from "@oration/canon/components/tabs";import { cn } from "@oration/canon/lib/utils";export function TabBar() {    const tabs = [        "Overview",        "Activity",        "Buying committee",        "Files",        "Notes",        "Emails",        "Meetings",    ];    return (        <div className="w-full max-w-sm overflow-hidden rounded-xl bg-card shadow-border">            <Tabs defaultValue="Overview" className="gap-0">                <div className="border-b border-border px-4">                    <TabsList variant="line" scrollable className="h-10 gap-3">                        {tabs.map((tab) => (                            <TabsTrigger key={tab} value={tab} className="px-0">                                {tab}                            </TabsTrigger>                        ))}                    </TabsList>                </div>                {tabs.map((tab) => (                    <TabsContent                        key={tab}                        value={tab}                        className={cn(                            "px-4 py-6 text-13 text-muted-foreground",                        )}                    >                        {tab} for Invoice automation rollout. Scroll the tabs                        sideways; the clipped edge fades over 2rem. With a tab                        focused, <Kbd>→</Kbd> moves to the next one.                    </TabsContent>                ))}            </Tabs>        </div>    );}

Stepper has no lost or skipped stage, so a deal that closed lost can't be drawn on the track; the product swaps the exit row for a sentence instead. Record pages also read synchronously today and draw no loading state.

Keyboard#

Keyboard interactions
KeysAction
JOpen the next record in the list you came from.
KOpen the previous record.
←→Move between tabs once the tab bar has focus.
EnterEdit an attribute value; Enter again commits it.
EscCancel an edit and restore the value.

Starter code#

RecordShell, then the record column. The sheet and the rail render the same aside.

A record page
// components/records/record/record-shell.tsxexport function RecordShell({ crumbs, actions, aside, asideTitle, children }: RecordShellProps) {  return (    <div className="flex h-svh min-h-0 flex-col">      <AppHeader        crumbs={crumbs}        actions={          <>            {actions}            {/* Below 1024px the rail moves into a sheet */}            <Sheet>              <SheetTrigger render={<Button variant="ghost" size="icon-sm" aria-label="Show details" className="lg:hidden" />}>                <PanelRightIcon />              </SheetTrigger>              <SheetContent side="right" className="w-[22rem] max-w-[90vw] gap-0 p-0">                <SheetHeader className="border-b border-border">                  <SheetTitle>{asideTitle}</SheetTitle>                  <SheetDescription className="sr-only">Record attributes</SheetDescription>                </SheetHeader>                <div className="min-h-0 flex-1 overflow-y-auto">{aside}</div>              </SheetContent>            </Sheet>          </>        }      />      <div className="flex min-h-0 flex-1">        <div className="min-w-0 flex-1 overflow-y-auto">{children}</div>        <aside aria-label={asideTitle} className="hidden w-[21rem] shrink-0 overflow-y-auto border-l border-border bg-background lg:block">          {aside}        </aside>      </div>    </div>  );}// The record column: hero, stage track, then line tabs that stick under the header<RecordShell crumbs={crumbs} actions={<RecordPager … />} aside={<DealProperties deal={deal} />} asideTitle={`${deal.name} details`}>  <RecordHero media={<MonogramTile name={company.name} color={company.color} size="xl" />} title={deal.name} subtitle={…} meta={…} actions={…} />  <div className="px-6 pb-5 sm:px-8">    <Stepper steps={stages} current={deal.stage} label="Deal stage" onStepSelect={changeStage} />  </div>  <Tabs defaultValue="overview" className="gap-0">    {/* top-0, not top-12: the record column is its own scroller under the header */}    <div className="sticky top-0 z-10 border-b border-border bg-background px-6 sm:px-8">      <TabsList variant="line" scrollable className="h-10 gap-3">        <TabsTrigger value="overview" className="px-0">Overview</TabsTrigger>        <TabsTrigger value="activity" className="px-0">Activity</TabsTrigger>      </TabsList>    </div>    <TabsContent value="overview" className="px-6 py-6 sm:px-8">      <div className="flex max-w-3xl flex-col gap-8">…</div>    </TabsContent>  </Tabs></RecordShell>
  • The tab bar sticks at top-0 of the record column, which is its own scroller under the 48px header. top-12 would leave a gap.
  • Name the rail with aria-label and the sheet with a title, both from the record name.

Rules that apply#

The Hairline-and-Lift Rule

A raised surface takes its edge and its lift from one composite shadow (shadow-border), never from a CSS border plus a shadow. CSS borders are for structural dividers only: table rules, header bottoms, section splits.

The Scroll Edge Rule

Clipped content shows its edge. Horizontal tab and view rows fade the clipped side over 2rem, and the last pinned grid column casts its edge shadow only once the grid scrolls sideways.

The Ink Fill Rule

Magnitudes are drawn in ink steps (80, 65, 45, 25, 15) on a Well Gray track: meters, progress, the stage track, ICP bars and the forecast bar. A fill turns semantic only when the value is itself a verdict, such as a strong ICP fit in Ledger Green. It is never indigo.

The Option Hue Rule

The ten categorical hues belong to select-option values (stage, lifecycle, tier, list and attribute options) and to identity tints on avatars, monogram tiles and the four app marks. An app hue appears in two places: the glyph tile, and the 3px pill beside the active sidebar item. Categorical hues never color-code sections, navigation text, status or charts.

Do and don't#

Do. Separate the attribute rail with a hairline left edge. It is part of the plane.
Don't. Lift the rail as a card with a shadow. It reads as a floating panel over the record.
Do. Draw the stage track in ink steps and keep stage color for the stage tag.
Don't. Color each stage segment with its tag hue. The track becomes a rainbow and the current stage stops standing out.
Do. Keep the rail in a sheet below 1024px, opened from the header.
Don't. Stack the attributes under the tabs on narrow screens, where nobody scrolls far enough to find them.