Layout
Layout
App surfaces in Bolt are composed from a small set of layout primitives from @norbital-ai/ui/layout . The primitives own geometry; the shell owns the app region and document scroll; and every surface follows the same body contract. Compile-time checks enforce this — a surface that breaks the rules fails the build, not just the review.
The primitives
Pick the primitive by the layout intent, not by what looks closest:
| Intent | Primitive | Notes |
|---|---|---|
| Vertical rhythm | Stack | Column of children with gap , optional align / justify |
| One row | Inline | Single horizontal line of children |
| Wrapping group | Cluster | Row that wraps; tags, chips, button groups |
| Two adaptive regions | Split | Named ratios ( rail , sidebar , third , half , wide ) and shared collapse tokens |
| Intrinsic grid | Grid | Auto-fit cells from a minimum token ( compact / card / panel |
| Exact spans | Columns + Column | Fixed counts (2/3/4/6) with explicit span |
| Top, main, bottom | Cover | Centers the body between optional top and bottom |
| Readable measure | Center | Constrain width to a measure ( narrow / reading / wide / full |
| Media crop | Frame | Fixed aspect ratio ( square / portrait / landscape / widescreen |
| Local scrolling | Bound + Scroll | Always a pair — never a bare overflow |
All primitives accept gap and pad from the token scale ( none , xs , sm , md , lg , xl ), an as element (a whitelist of flow containers such as section , article , main , form ), and their named props. Parents choose the layout algorithm; children do not request growth.
The app body contract
Every app is one Cover with a top heading slot and exactly one body region:
┌───────────────────────────────────────────────┐
│ Cover top={pageHeading} │
│ ┌─────────────────────────────────────────┐ │
│ │ PageHeader title="Tasks" … │ │
│ └─────────────────────────────────────────┘ │
│ │
│ body region (one of three, below) │
│ ┌─────────────────────────────────────────┐ │
│ │ Scroll name="tasks" inset │ │
│ │ ┌───────────────────────────────────┐ │ │
│ │ │ CollectionTable collection="tasks"│ │ │
│ │ └───────────────────────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘ There are exactly three legal body regions, and each owns both scroll and the app inset:
| Body | Owns scroll + inset |
|---|---|
<Tabs … /> | , automatically |
<Scroll name="…" inset> for flowing content | the |
<Bound size="full" inset> for one self-scrolling component | the |
<script lang="ts">
import { CollectionTable } from '@norbital-ai/ui/collection-table';
import { Bound, Cover, Scroll } from '@norbital-ai/ui/layout';
import { PageHeader } from '@norbital-ai/ui/page-header';
</script>
<svelte:head>
<title>Tasks</title>
<meta name="bolt:icon" content="lucide:list-todo" />
</svelte:head>
{#snippet pageHeading()}
<PageHeader title="Tasks" description="Everything the team is working on." />
{/snippet}
<Cover as="main" top={pageHeading}>
<Scroll name="tasks-scroll" inset>
<CollectionTable collection="tasks">
{#snippet columns({ Column })}
<Column name="title" />
<Column name="status" />
{/snippet}
</CollectionTable>
</Scroll>
</Cover> Scrolling: one owner per axis
The shell owns the document scroll; every local scroll region is an explicit Bound + Scroll pair. Each ancestor chain has one scroll owner per axis and one inset owner ; sibling panes may own their own. The rules that fall out of this are enforced at compile time:
- No generic
overflowwrappers,overflow-hiddenclipping, or raw flex/min-size scroll chains. - No raw structural flex/grid containers, and no margins between siblings — spacing is the primitives’
gap. - No literal
px-4 sm:px-6-style classes — the app inset is the primitives’inset. - No
styleattribute on a layout primitive — use its named props. - Clipping is valid only for text truncation,
Framemedia, or an audited popup/sheet boundary.
Responsiveness without breakpoint recipes
Splittakescollapse(stack,switch,none) and a sharedcollapseAttoken instead of arbitrary widths.Gridis intrinsic: it auto-fits cells to the available space from aminimumtoken. UseColumnsonly when an exact count or span matters.Centerkeeps reading width without hand-rolled media queries. Prefer intrinsic layout and shared container-query tokens over viewport breakpoint recipes.
src/apps/ is checked the same way. See UI components for how forms fit into a surface.Related guides
- Apps — app metadata and client access
- UI components — collection surfaces inside a body region
- Navigation state — record-detail stacks and the sidesheet