Page structure
The structural component for what goes inside <main>.
.page is a flex-column that owns the rhythm between its top-level children via --page-section-gap. The header and sections carry no outer margin themselves. No JS, pure CSS.
The page wrapper
Drop a .page inside .app-shell__main. Top-level children stack vertically with the section gap.
Customers
First section content.
Second section content.
<div class="page p-4 w-100" style="border: 1px solid var(--st-border); border-radius: var(--st-radius); background: var(--st-background);">
<header class="page__header">
<div class="page__title">
<h1 class="h3">Customers</h1>
</div>
</header>
<section class="page__section">
<p class="text-muted-foreground">First section content.</p>
</section>
<section class="page__section">
<p class="text-muted-foreground">Second section content.</p>
</section>
</div>Page header
.page__header is a flex row with a .page__title slot on the leading edge and a .page__action slot on the trailing edge. Both slots are passthrough. Title takes a heading with an optional subtitle, actions takes any button row.
Customers
All accounts in this workspace.
<div class="page p-4 w-100" style="border: 1px solid var(--st-border); border-radius: var(--st-radius); background: var(--st-background);">
<header class="page__header">
<div class="page__title">
<h1 class="h3">Customers</h1>
<p class="text-muted-foreground">All accounts in this workspace.</p>
</div>
<div class="page__action">
<button type="button" class="btn btn--outline btn--neutral"><i data-lucide="download"></i>Export</button>
<button type="button" class="btn btn--primary"><i data-lucide="plus"></i>Add customer</button>
</div>
</header>
</div>Without actions
Drop the action slot. The title block sits alone.
Settings
Manage your account, billing, and integrations.
<div class="page p-4 w-100" style="border: 1px solid var(--st-border); border-radius: var(--st-radius); background: var(--st-background);">
<header class="page__header">
<div class="page__title">
<h1 class="h3">Settings</h1>
<p class="text-muted-foreground">Manage your account, billing, and integrations.</p>
</div>
</header>
</div>Wraps on narrow widths
The header is flex-wrap: wrap. When the viewport gets too narrow for the title and action slot to share a row, the actions drop below.
<header class="page__header">
<div class="page__title">
<h1 class="h3">Customers</h1>
<p class="text-muted-foreground">All accounts in this workspace.</p>
</div>
<div class="page__action">
<button type="button" class="btn btn--primary">Add customer</button>
</div>
</header>Sections
.page__section is a flex-column for each block inside the page. The .page parent supplies the gap between sections; each section supplies its own inner gap via --page-section-inner-gap.
Overview
Headline stats and the period selector live here.
Recent activity
A timeline of the last events on this account.
Quick actions
Shortcuts to the most-used flows.
<div class="page p-4 w-100" style="border: 1px solid var(--st-border); border-radius: var(--st-radius); background: var(--st-background);">
<section class="page__section">
<h2 class="page__section-title">Overview</h2>
<p class="text-muted-foreground">Headline stats and the period selector live here.</p>
</section>
<section class="page__section">
<h2 class="page__section-title">Recent activity</h2>
<p class="text-muted-foreground">A timeline of the last events on this account.</p>
</section>
<section class="page__section">
<h2 class="page__section-title">Quick actions</h2>
<p class="text-muted-foreground">Shortcuts to the most-used flows.</p>
</section>
</div>With section header + actions
For sections that need their own action row, wrap the title in .page__section-header and reuse .page__action as the slot. Same flex recipe at a tighter scale.
Active customers
1,284 accounts.
<div class="page p-4 w-100" style="border: 1px solid var(--st-border); border-radius: var(--st-radius); background: var(--st-background);">
<section class="page__section">
<header class="page__section-header">
<h2 class="page__section-title">Active customers</h2>
<div class="page__action">
<button type="button" class="btn btn--outline btn--neutral btn--sm">Filter</button>
<button type="button" class="btn btn--primary btn--sm">Invite</button>
</div>
</header>
<p class="text-muted-foreground">1,284 accounts.</p>
</section>
</div>Content width
.page is unopinionated about width. Wrap it (or set its own width on a parent) to clamp the content column.
- Full width (
.container-fluidor no container) for dashboards, tables, anything dense. - Breakpoint-clamped (
.container) for marketing pages, or anything that should match the standard column widths. - Custom max-width (40 to 60rem) for settings forms, articles, anything text-heavy where long lines hurt readability.
<!-- Full width -->
<main class="app-shell__main">
<div class="page container-fluid p-4">...</div>
</main>
<!-- Breakpoint-clamped -->
<main class="app-shell__main">
<div class="page container p-4">...</div>
</main>
<!-- Custom narrow column for reading -->
<main class="app-shell__main">
<div class="page p-4" style="margin-inline: auto; max-width: 48rem;">...</div>
</main>Putting it together
Page header, sections, and a section with its own header, all inside one .page that supplies the rhythm.
Customers
All accounts in this workspace.
Active
1,284 customers.
Inactive
312 customers.
<div class="page p-4 w-100" style="border: 1px solid var(--st-border); border-radius: var(--st-radius); background: var(--st-background);">
<header class="page__header">
<div class="page__title">
<h1 class="h3">Customers</h1>
<p class="text-muted-foreground">All accounts in this workspace.</p>
</div>
<div class="page__action">
<button type="button" class="btn btn--outline btn--neutral"><i data-lucide="download"></i>Export</button>
<button type="button" class="btn btn--primary"><i data-lucide="plus"></i>Add customer</button>
</div>
</header>
<section class="page__section">
<header class="page__section-header">
<h2 class="page__section-title">Active</h2>
<div class="page__action">
<button type="button" class="btn btn--outline btn--neutral btn--sm">Filter</button>
</div>
</header>
<p class="text-muted-foreground">1,284 customers.</p>
</section>
<section class="page__section">
<h2 class="page__section-title">Inactive</h2>
<p class="text-muted-foreground">312 customers.</p>
</section>
</div>Customization
Eight variables retune .page without touching component CSS. Override on .page itself, on a parent scope, or on :root. The cascade scopes the change.
| Variable | Default | Use |
|---|---|---|
--page-section-gap |
calc(1.5rem * var(--st-density)) | Vertical rhythm between top-level children of .page |
--page-header-gap |
calc(1rem * var(--st-density)) | Horizontal space between title and action slot in .page__header |
--page-title-gap |
calc(0.25rem * var(--st-density)) | Vertical gap between heading and subtitle inside .page__title |
--page-action-gap |
calc(0.5rem * var(--st-density)) | Space between buttons inside .page__action |
--page-section-inner-gap |
calc(1rem * var(--st-density)) | Vertical gap between header and content inside .page__section |
--page-section-header-gap |
calc(0.75rem * var(--st-density)) | Horizontal space between title and action slot in .page__section-header |
--page-section-title-size |
1.125rem | Font size of .page__section-title |
--page-section-title-weight |
600 | Font weight of .page__section-title |