Page layout

The skeleton every app page follows

The rules — how every app page is put together

  • A page is {layout $_shell} + {block content} — the shell renders the whole chrome (sidebar, topbar, footer).
  • The route's title, subtitle and breadcrumbs (route.php) render in the topbar — never write your own in-page <h1>. The one exception is the page-header block below.
  • Content is cards on the grid. A card owns its vertical spacing (its margin-bottom equals the content padding) — never add mt-*/mb-* between stacked cards; the rhythm on this very page comes from zero spacing utilities.
  • One .btn-primary per page (its main action, in the page header) and at most one per card (in the card header) — everything else is .btn-secondary.
  • Theme-aware always: muted text is .text-body-secondary, colored chips ride the bg-*-subtle text-*-emphasis pairs, borders use var(--bs-border-color) — never hardcoded colors. Both themes and mobile are part of done.

Page header with actions — only when the page has page-level actions

  • A default page renders no in-page header — the topbar already shows the route title. Add .page-header only when the page carries page-level actions (or a summary line worth keeping in-page).
  • The title is the route's own ($page->title) in .page-title — never a different text than the topbar.
  • Actions sit right, in .page-header-actions.d-flex.gap-2: at most ONE .btn-primary (icon + label), secondaries as .btn-secondary.

The standard grids — pick one of these three

Stat row — four up: col-12 col-sm-6 col-xl-3
stat
stat
stat
stat
Content + aside (the default two-column page) — col-12 col-xl-7 / col-12 col-xl-5
main — the list / table / form
aside — secondary cards
Halves — col-12 col-lg-6
half
half
  • Always start from col-12 — pages are mobile-first and columns stack on small screens.
  • Cards in columns use the plain .row — its default gutter matches the page rhythm (the tinted boxes above are a visualization, not cards; g-3 there is demo-only).
  • A page that is ONE data list is the full-width lister, no grid at all (see the data-list page) — data lists are lister CARDS by default, not html tables (tables).
  • Do not invent other splits: stat row, 7/5, halves and full-width cover the app pages; nesting cards inside cards is forbidden.

Where the rest lives

  • Docs (the contract): templates/app/docs/components/layout.md — anatomy, adding a page; cards.md — card variants; foundation.md — tokens, spacing, themes.
  • UI vocabulary (buttons, badges, alerts, toasts): /standards/ui.
  • Empty / loading / error states: /standards/states.
  • Full page compositions: dashboard, data list, detail, settings.