Layouts
tier 2 — classesTier 1, like the shell itself: two more page shells, a collapsible nav group, and the skip link. All of it must look the same in every app.
Auth layout
Sign-in, sign-out, “check your e-mail”, an invitation. One centred card and nothing else: there is no navigation to offer somebody who is not signed in, and a half-populated sidebar behind a sign-in form invites them to try it.
.auth-card is capped rather than stretched. A sign-in form the width of a
desktop screen has inputs nobody can aim at, and reads as an unfinished page.
Use your organisation account.
Trouble signing in? Ask the service desk.
<div class="auth-layout" style="min-height:0; padding:24px">
<div class="auth-brand">
<span class="brand-logo" style="width:32px; height:32px; background:var(--brand)"></span>
Approval Console
</div>
<div class="card auth-card">
<div class="card-head"><strong>Sign in</strong></div>
<div class="card-body dr-col">
<p class="form-hint">Use your organisation account.</p>
<button class="btn btn-primary"><i class="ri-microsoft-fill"></i> Continue with Microsoft</button>
</div>
</div>
<p class="auth-foot">Trouble signing in? Ask the service desk.</p>
</div>
Full-bleed layout
For content that owns the whole area below the header — a map, a board, a diagram editor.
.page is the frame's scroll container and pads its content;
.full-layout replaces it, taking the padding off and handing scrolling to the
child.
Used instead of .page, not alongside it —
<AppShell PageClass="…"> cannot remove .page's own padding,
so this one case writes the shell markup by hand.
<div class="layout">
<aside class="sidebar">…</aside>
<div class="content">
<header class="topbar">…</header>
<div class="full-layout">
<div class="toolbar">…</div>
<div style="flex:1; min-height:0">The map fills whatever is left.</div>
</div>
</div>
</div>
Collapsible nav group
For a section with more children than fits as a flat list. Built on
<details>/<summary>, so the open state, the keyboard
operation and the announcement come from the platform — and the group stays usable with
scripting blocked, which the frame must be.
Put open on the group containing the current page, so a reader never lands on a
page whose own nav entry is hidden. Sub-items indent to sit under the parent's
label, not its icon. In the collapsed rail the whole group flattens to icons.
<aside class="sidebar" style="height:320px">
<nav class="nav">
<div class="nav-scroll">
<a class="nav-link" href="#"><i class="ri-inbox-line"></i><span>Orders</span></a>
<details class="nav-group" open>
<summary><i class="ri-building-line"></i><span>Fulfilment</span></summary>
<a class="nav-link active" href="#"><span>Ready to pick</span><span class="nav-count">41</span></a>
<a class="nav-link" href="#"><span>On hold</span></a>
</details>
<details class="nav-group">
<summary><i class="ri-settings-3-line"></i><span>Administration</span></summary>
<a class="nav-link" href="#"><span>Warehouses</span></a>
<a class="nav-link" href="#"><span>Audit log</span></a>
</details>
</div>
<div class="nav-tools">
<a class="nav-link nav-link-tool" href="#"><i class="ri-question-line"></i><span>Support</span></a>
<hr class="nav-tools-sep" />
<a class="nav-link nav-link-tool" href="#"><i class="ri-book-2-line"></i><span>Documentation</span><i class="ri-external-link-line nav-link-ext"></i></a>
</div>
</nav>
</aside>
Skip link
The first focusable thing on the page, jumping past the navigation to the content. Without it a keyboard user tabs through every nav item on every page — which is the single most common reason a shell like this is exhausting to use without a mouse.
It is off-screen rather than hidden, so it stays focusable, and it appears above everything
when focused. tabindex="-1" on <main> is not optional: without
it the jump only scrolls, and focus stays back in the navigation where it was.
<body>
<a class="skip-link" href="#main">Skip to content</a>
<div class="layout">
<aside class="sidebar">…</aside>
<div class="content">
<header class="topbar">…</header>
<div class="page">
<main id="main" tabindex="-1">…</main>
</div>
</div>
</div>
</body>