Layout
Application shell: sidebar rail and floating work panel. Tokens: Colors (surface-app, surface-page), Spacing. Responsive rules for narrow viewports are documented separately when added.
This page is layout guidance. Strongly recommended on every app screen: sidebar → page panel → page header → scrollable page content. That shell is fixed; only the optional scope bar sits above the header when scoped. Inside page content, patterns (filter row, list card, split columns, etc.) are optional — pick per page. Copy, entities, and controls are not prescribed here.
App shell
One outer horizontal flex row, full viewport (100vw × 100vh), background surface-app (#E8E0D7) — the desk the rail and work panel sit on.
| Region | Role |
|---|---|
| Sidebar | Fixed 248px, full height, flush on the rail (same surface-app). Does not scroll with page content. |
| Page panel | flex: 1, background surface-page (#F4F0EA), rounded-4 (18px), box-shadow: var(--sq-shadow-lg), margin 10px 10px 10px 0 — floats above the desk on the top, right, and bottom; the rail stays visible on the left. |
Inside the page panel: optional scope bar, then page header (fixed), then page content (scrolls). Only page content uses flex-grow-1 overflow-auto + min-height: 0.
Recommended stack (top → bottom):
| Layer | Required? |
|---|---|
| Scope bar | Optional — scoped context only |
| Page header | Yes — title/actions; density varies |
| Page content | Yes — everything else scrolls here |
Within page content, the filter row and other patterns are optional.
html
<!-- Structure only — omit optional blocks per screen -->
<div class="d-flex vh-100" style="background: var(--sq-surface-app)">
<aside><!-- 248px sidenav --></aside>
<div
class="sq-panel flex-grow-1 d-flex flex-column"
style="margin: 10px 10px 10px 0"
>
<div class="sq-scope-bar" role="region"><!-- optional --></div>
<header class="border-bottom flex-shrink-0">
<!-- page header (recommended) -->
</header>
<div class="flex-grow-1 overflow-auto min-h-0">
<!-- page content -->
<div><!-- optional filter row --></div>
<!-- page body: lists, cards, forms, … -->
</div>
</div>
</div>Use sq-panel (Sqrl custom — same as overflow-hidden border rounded-4; tag on Scope bar below) when the scope bar caps the panel (Navigation); otherwise the raised surface-page shell alone is enough.
Sidebar rail
Top to bottom (Navigation):
- Scope control — only selector in the rail (Scope switch; workspace, tenant, or account).
- Divider
- Primary nav — flat list; icon + label + optional mono count; one
active+aria-current="page". Labels and order: Navigation. - Footer (
margin-top: auto) — search (⌘K), then user row (avatar, name, profile link, sign-out control).
Scope bar
When the UI is scoped to one context (one record, project, tenant slice, etc.), show sq-scope-bar at the top of the work panel — above the page header. It names the scope (title + optional meta) and one exit control back to the wider app; it is not a second primary nav.
- Rail: scope control enters/exits scope; nav items may reorder (Navigation).
- Panel: bar flush to the panel top inside
sq-panel; header and content sit below. - Same markup/tokens for any scope type — only labels and icon change.
Page header
Top of the page panel after an optional scope bar. Fixed above page content (recommended on every screen). The filter row is not part of the header — it lives inside page content. The <header> spans the full width of the panel. Put border-bottom on that outer element so the rule runs edge to edge; padding lives on an inner wrapper only (px-4, pt-3, pb-3 — match list content inset, usually 16px horizontal).
breadcrumb (optional), Newsreader h1, optional subtitle (text-muted), primary action on the right with align-items-start.
Accounts
24 companies in this view
html
<header class="border-bottom">
<div class="px-4 pt-3 pb-3">
<nav aria-label="breadcrumb" class="mb-2">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Accounts</li>
</ol>
</nav>
<div class="d-flex align-items-start justify-content-between gap-3">
<div>
<h1>Accounts</h1>
<p class="text-muted mb-0">
<span class="font-monospace">24</span> companies in this view
</p>
</div>
<button
type="button"
class="btn btn-primary flex-shrink-0 align-self-start"
>
<i class="bi bi-plus-lg me-1" aria-hidden="true"></i>
New account
</button>
</div>
</div>
</header>Page content
Everything below the page header: tables, cards, search, filters, forms, empty states, section grids, optional filter row, etc. Fills the rest of the page panel and scrolls as one block (unless a nested region scrolls on its own, e.g. overflow-auto + sq-table-sticky-head).
There is no single mandatory layout inside this region. The subsections below are optional patterns (list shell, overview grid, split columns, form stacks) — use, merge, or skip them per page.
Loading content
While data or a view is still fetching, keep fixed chrome (page header; optional filter row when tab labels are already known) and fill page content with skeleton placeholders shaped like the loaded layout — same inset, card shell, or table band you will show when ready. Do not leave the scroll region blank or use a full-page spinner; swap skeletons for real markup when the response arrives.
Filter row
Optional. Part of page content, not the page header — when used, it usually sits at the top of the scroll region (first band below the header). It scrolls with the body unless you make it sticky. Many pages omit it entirely.
Full width within the content inset; same horizontal padding as the header (px-4). Typical layout: property filters / other page content on the left (pill tabs — All / Mine / Active); presentation of the same data on the right (table ↔ cards via segmented; sort dropdown) — align-items-start, justify-content-between. Exact controls depend on the page. Tabs vs segmented vs pill radio → Navigation — Choose a control.
html
<div
class="px-4 d-flex flex-wrap align-items-start justify-content-between gap-3"
>
<ul class="nav nav-pills mb-0">
<li class="nav-item">
<a class="nav-link active" href="#" aria-current="page"
>All <span class="font-monospace">24</span></a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#"
>Active <span class="font-monospace">19</span></a
>
</li>
<li class="nav-item">
<a class="nav-link" href="#"
>Inactive <span class="font-monospace">5</span></a
>
</li>
</ul>
<div class="d-flex align-items-center gap-2">
<div class="dropdown">
<button
type="button"
class="btn btn-sm btn-ghost fw-medium text-body py-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i class="bi bi-arrow-down-up me-1" aria-hidden="true"></i>
Name (A-Z)
</button>
<div class="dropdown-menu dropdown-menu-end">
<button type="button" class="dropdown-item active" aria-current="true">
Name (A-Z)
</button>
<button type="button" class="dropdown-item">Name (Z-A)</button>
<button type="button" class="dropdown-item">
Quote potential (high-low)
</button>
<button type="button" class="dropdown-item">
Quote potential (low-high)
</button>
</div>
</div>
<div class="sq-segmented sq-segmented-sm" role="group" aria-label="Layout">
<input
type="radio"
class="btn-check"
name="layout"
id="layout-list"
autocomplete="off"
checked
aria-label="List"
/>
<label class="btn" for="layout-list"
><i class="bi bi-list-ul" aria-hidden="true"></i
></label>
<input
type="radio"
class="btn-check"
name="layout"
id="layout-grid"
autocomplete="off"
aria-label="Grid"
/>
<label class="btn" for="layout-grid"
><i class="bi bi-grid" aria-hidden="true"></i
></label>
</div>
</div>
</div>Padding
| Context | Horizontal / vertical inset |
|---|---|
| Dense list / table pages | 16px (px-4, pb-4) — align with header/filter px-4 |
| Looser overview / section grids | 32px page gutter (p-4 on the scroll container or section wrappers) — Spacing |
List and table pages (optional pattern)
When the main body is a data table, an optional filter row can sit above a raised container on surface-page — often card-bordered so the toolbar and grid read as one object; pagination sits below the card on surface-page, not in card-footer.
Typical vertical stack (Table):
- Toolbar — search, facet
form-selectfilters, secondary actions as needed; oftenborder-bottombefore the grid. table.table(Table — optionaloverflow-hiddenwhen not inside a card). Tables andtheadare square (norounded). Inside a card, wrap the table inmx-3so it does not flush to the card edges.- Pagination — summary + pages outside the card (same horizontal inset as the list — e.g.
px-4on the scroll area).
Guideline: property filters and other page content as pill tabs in the filter row; search and field filters in the toolbar; table ↔ cards as segmented — Choose a control.
| Name | City | Status | Quote potential |
|---|---|---|---|
| Van Dijk Logistics | Rotterdam | Active | € 132.000 |
| North Sea Marine | Den Helder | Inactive | € 78.000 |
Showing 1–10 of 24
Overview-style pages (optional pattern)
Hub or summary pages: often 32px gutter (Spacing), no filter row; body is cards and/or sections in the scroll area (intro block may live in the header or content — either is fine).
Preferred (optional): row / g-4 with mixed column widths and at least one row that breaks the rhythm — avoid a single column of identical boxes.
Split content (optional pattern)
When one full-width column is not enough, page content may use a horizontal split (this is not the 248px app sidebar):
| Column | Role |
|---|---|
| Secondary column (fixed width) | Optional local nav, outline, progress, or meta — whatever the page needs; scrolls with the page or sticky within the split. |
| Primary column | Main body — detail, forms, cards, etc. |
Often paired with a scope bar and/or a compact page header, but the split is a layout option, not a requirement for scoped or multi-step flows. Default list pages stay single column.
Forms and detail blocks (optional pattern)
Full-width field rows on surface-page or inside a card: border-subtle between rows; no border on the last row. Destructive or save actions belong in the page header when they apply to the whole page, not repeated on every row.
Rules
- Default shell (strongly recommended): sidebar (248px) + page panel (
surface-page,rounded-4,--sq-shadow-lg, 10px margin top/right/bottom) + page header + scrollable page content. Do not drop parts of this shell without a clear reason. surface-app= outer desk behind the sidebar; only page content scrolls (unless a deliberate inner scroll on table wrap or split layout).- Primary nav structure and labels: Navigation.
- Scope: rail scope control + optional
sq-scope-baron scoped screens — Scope bar. - One
btn-primaryin the page header title row when that row has a primary action; Newsreaderh1. Full-width<header>withborder-bottomon the outer element; padding on an inner wrapper. Optional breadcrumb or micro-caps eyebrow — not both unless the hierarchy needs it. - Fixed chrome = scope bar (if any) + page header; page content =
overflow-auto+min-height: 0, with optional filter row as the first block when needed. - List pattern (optional): filter row → bordered shell → toolbar → table wrap → pagination; property filters vs presentation vs search as in List and table pages.
- Prefer the simplest stack — omit filter row, card shell, or split content when a single column suffices.
- Loading: skeleton placeholders in page content only — Loading content; empty lists use empty state, not skeletons.