Skip to content

Layout ​

Bootstrap Sqrl custom

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.

RegionRole
SidebarFixed 248px, full height, flush on the rail (same surface-app). Does not scroll with page content.
Page panelflex: 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):

LayerRequired?
Scope barOptional — scoped context only
Page headerYes — title/actions; density varies
Page contentYes — 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.

Sqrl custom

Top to bottom (Navigation):

  1. Scope control — only selector in the rail (Scope switch; workspace, tenant, or account).
  2. Divider
  3. Primary nav — flat list; icon + label + optional mono count; one active + aria-current="page". Labels and order: Navigation.
  4. Footer (margin-top: auto) — search (⌘K), then user row (avatar, name, profile link, sign-out control).

Scope bar ​

Sqrl custom

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.

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 ​

ContextHorizontal / vertical inset
Dense list / table pages16px (px-4, pb-4) — align with header/filter px-4
Looser overview / section grids32px 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):

  1. Toolbar — search, facet form-select filters, secondary actions as needed; often border-bottom before the grid.
  2. table.table (Table — optional overflow-hidden when not inside a card). Tables and thead are square (no rounded). Inside a card, wrap the table in mx-3 so it does not flush to the card edges.
  3. Pagination — summary + pages outside the card (same horizontal inset as the list — e.g. px-4 on 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.

NameCityStatusQuote potential
Van Dijk LogisticsRotterdamActive€ 132.000
North Sea MarineDen HelderInactive€ 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):

ColumnRole
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 columnMain 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 ​

  1. 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.
  2. surface-app = outer desk behind the sidebar; only page content scrolls (unless a deliberate inner scroll on table wrap or split layout).
  3. Primary nav structure and labels: Navigation.
  4. Scope: rail scope control + optional sq-scope-bar on scoped screens — Scope bar.
  5. One btn-primary in the page header title row when that row has a primary action; Newsreader h1. Full-width <header> with border-bottom on the outer element; padding on an inner wrapper. Optional breadcrumb or micro-caps eyebrow — not both unless the hierarchy needs it.
  6. 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.
  7. List pattern (optional): filter row → bordered shell → toolbar → table wrap → pagination; property filters vs presentation vs search as in List and table pages.
  8. Prefer the simplest stack — omit filter row, card shell, or split content when a single column suffices.
  9. Loading: skeleton placeholders in page content only — Loading content; empty lists use empty state, not skeletons.