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