Detail pages

A list gets someone to a record; the detail page is the record. This is the transaction page, measured: a 1024px column centred in the island, the receipt on the left at 576px, a 264px properties rail on the right that sticks while the receipt scrolls. Customers and payouts are the same page with different panels.

The receipt opens with a hero — the order number and status pill on one line, then the amount at 44px with the date beside it — and continues as panels, each a tiny uppercase header over a bordered card. The rail opens with the record’s actions as a cluster of chips, then Properties: the facts you look up rather than read, as icon-led rows with no labels. Everything else waits in disclosures.

Detail page.folio-detail
#00244
Succeeded

$77.50

Sep 1, 2026 at 10:07 PM

Order

After Effects Bundles

Qty 1 · $250.00

$250.00
Subtotal$250.00
Discount (AEEFFECTS)-$172.50
Total$77.50

Notes

The rail's chips are grey at rest and only turn red on hover, so a record you are merely looking at does not read as a wall of danger.

tsx
import {
  DetailPage, DetailMain, DetailAside, DetailHero, DetailSkeleton,
  Block, BlockRow, BlockFoot, BlockLine, BlockText,
  RailActions, RailAction, Properties, Property, Disclosures, Disclosure, Field,
} from '@/components/detail-page'

if (loading) return <DetailSkeleton actions={2} />   // the same containers, so nothing moves

<DetailPage>
  <DetailMain>
    <DetailHero label="#00244" badge={<Badge variant="success">Succeeded</Badge>} title="$77.50" meta="Sep 1, 2026 at 10:07 PM" />

    <Block title="Order">
      <BlockRow tile={<img src={image} />} title="After Effects Bundles" dim="Qty 1 · $250.00" value="$250.00" />
      <BlockFoot>
        <BlockLine label="Subtotal" value="$250.00" />
        <BlockLine label="Discount (AEEFFECTS)" value="-$172.50" tone="success" />
        <BlockLine total label="Total" value="$77.50" />
      </BlockFoot>
    </Block>
  </DetailMain>

  <DetailAside>
    <RailActions>
      <RailAction icon={<UndoSolid />} destructive onClick={refund}>Refund</RailAction>
    </RailActions>
    <Properties>
      <Property lead icon={<UserSolid />}>Card Holder Name</Property>
      <Property icon={<MailSolid />} href="mailto:[email protected]" copy="[email protected]">[email protected]</Property>
      <Property icon={<CalendarSolid />}>Sep 1, 2026 at 10:07 PM</Property>
    </Properties>
    <Disclosures>
      <Disclosure title="Identifiers">
        <Field label="Transaction ID" value={id} copy={id} />
      </Disclosure>
    </Disclosures>
  </DetailAside>
</DetailPage>

Actions that are not rail chips — and the chips again, out of habit — go in the dashboard’s header bar. The transaction page does exactly this: the rail is where you find Refund, the ⋯ menu is where you reach for it.

tsx
useTitleBar({
  crumbs: [
    { id: 'list', label: 'Deliveries', onClick: () => nav('/deliveries') },
    { id: 'one', label: title, loading: !loaded },      // skeleton pill until the record arrives
  ],
  menu: [
    { label: 'Send again', icon: 'send', onAction: resend },
    { label: 'Revoke', icon: 'trash', destructive: true, onAction: revoke },
  ],
})

Rules

  • The hero states the record: label (an order number) at 20px muted, the headline (an amount or a name) at 44px, the status beside the label, the date beside the headline. Never a page title repeating the breadcrumb.
  • Panels are an uppercase 12px header over a bordered, shadowed card. Rows inside are 44px tiles with a title and a dim sub line; the totals band is tinted and separated by a hairline.
  • Rail chips are 32px, filled, muted. Destructive ones only turn red on hover. Two or three at most; more than that belongs in the ⋯ menu.
  • Properties have no labels. A glyph says what a row is; the value is the row. Anything that needs a label or a second line is a Field inside a Disclosure.
  • Load behind DetailSkeleton: it is built from the same containers, so the columns do not move when the record lands.