Layout principles
Tokens describe surfaces. Layout principles describe pages: where the nav lives, how wide the content area is, how a layout is composed from blocks, which overlays you reach for, and when each pattern applies. v2 is preview-first — every principle renders as a live structural outline you edit in place.
Four kinds of principle
| Kind | Captures |
|---|---|
| Shell | The outer frame: content max-width, padding, breakpoints, transition speed. One per app surface (app / marketing / docs / auth / custom). |
| Layout | The content pattern, modelled as an ordered set of blocks (each with a usage + flex/grid/spacing). Variants: dashboard / list / detail / article / settings / split / full-width. |
| Overlay | Drawer, modal, toast, popover, or full-page overlay — each with its own per-kind config (placement, size, dismiss behaviour…). |
| Navigation | Ordered nav levels for a specific container. See below. |
Navigation lives in one place
Shells and layouts carry no navigation fields of their own. All navigation
is a first-class NavigationPrinciple bound to a specific container — a shell,
layout, modal, drawer, popover, or full-page overlay. Each container has ordered
nav levels (first nav, second nav, …); every level picks:
- a component (
horizontal-nav,vertical-nav,breadcrumb,tabs, …), - a location (
header,left,right, …), and - its own rules of application (use / avoid).
Valid components are constrained per container — a popover can host tabs but
not a vertical-nav. The allow-list lives in
tagTaxonomy.ts.
Example — the default app shell: first nav is a horizontal nav in the header (global features); second nav is a vertical nav on the left (app sections).
Application tags (use / avoid)
Every principle carries structured rules of application instead of free-form
notes, so the Spread and AI agents can both match a page to a layout and rule
one out. A tag is key:value with a polarity:
- When to use — e.g.
content-type: dashboard,has-filters: yes - When NOT to use — anti-pattern tags, e.g.
content-type: article
The seed taxonomy ships content-type, has-filters, and audience; any new
key:value you type joins the suggestion pool.
A default preset ships on first run
A fresh library is never empty. It seeds one app shell, its bound navigation,
seven layouts, four overlays (drawer / modal / full-page / toast), and a
z-index stacking order (toast > modal > drawer > popover > tooltip). You're
tweaking from day one, not starting from a blank form. Reset to default
preset (header "…" menu) restores this exact state.
Live structural preview
The right pane is an iframe (the same preview-host that renders components) on
new hash routes — #shell/<id>, #layout/<id>, #overlay/<id>, #nav/<id>.
It outlines the shell's header / content margins / nav levels, draws a layout's
blocks (each labelled with its usage), and renders overlays open against the
shell. Edits propagate over a tostada:principles postMessage channel and
repaint live. Clicking a region in the preview opens its inline editor. If the
preview-host isn't running, a fallback notice appears and the inline editors
keep working.
Why this matters
A team that captures "dashboards use a 4-column stats row, the left nav is for app sections, drawers open from the right" turns vague Slack threads into one explicit, machine-readable rule that ships in the export. Every AI agent reading it then builds the same structure without being told again.
Where this lives in code
- Types:
src/principles/v2/types.ts - Default preset:
src/principles/v2/defaults.ts - Tag taxonomy + nav allow-list:
src/principles/v2/tagTaxonomy.ts - Editor:
src/components/pages/properties/LayoutPrinciplesPageV2.tsx - Preview renderers:
preview-host/src/principles/ - Authoring UI: Library → Properties → Layout principles