Layout
Work frame-first: decide who owns global chrome, which regions scroll, and how content reflows before choosing decorative details.
The three composition levels
Primitive
One reusable interaction or visual contract, such as button, field, dialog, or table.
Block
One focused section composed from primitives. It never owns the application viewport.
Page
An ordered content frame. Shell pages are the explicit exception that also own global navigation.
Shell ownership
Your host layout owns document metadata, global navigation, session controls, and the main viewport. A content page owns its page header, local navigation, and content regions. Avoid nested full-height shells and competing scroll containers: install a shell page only when Brok should own global navigation, and never nest one inside your existing shell.
{{-- resources/views/layouts/app.blade.php — the host owns the frame --}}
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>…</head>
<body class="min-h-svh bg-background text-foreground">
<x-pages.app-shell-bordered>
{{-- A content page owns only what is inside the frame. --}}
{{ $slot }}
</x-pages.app-shell-bordered>
</body>
</html>
Layout grammar
Build screens from eight constrained layout primitives. Their props map to literal, discoverable classes and unknown values fall back safely, so feature code never needs an arbitrary pixel gap or a margin chain.
| Primitive | Use it for | Props |
|---|---|---|
| section | Page rhythm — vertical space between bands of content | space, surface, as |
| container | A bounded, centred reading width with gutters | size, gutter, centered, as |
| stack | One-direction flow with a fixed gap (forms, toolbars) | direction, gap, align, justify, wrap, divider, as |
| cluster | A wrapping inline group (tags, actions) | gap, align, justify, wrap, as |
| grid | Columns for collections that reflow | cols, gap, as |
| surface | A panel — tone, padding, radius, shadow in one place | tone, padding, radius, shadow, as |
| prose | Readable long-form text at a measured width | size, centered, as |
| bleed | Escape the reading column for a wide table or code block | size, scroll, as |
Every gap and padding value resolves to a token (gap-stack-*,
py-section-*, px-gutter-*), so density and
theme changes reach every screen without touching a view.
Composed example
The same markup, as a page would use it and as it renders with the default tokens:
<brok:section space="lg" surface="muted">
<brok:container size="lg">
<brok:stack gap="lg">
<brok:prose :centered="false">
<h2>Workspace activity</h2>
<p>Readable copy wraps safely, including long translated strings and URLs.</p>
</brok:prose>
<brok:grid cols="3" gap="md">
<brok:surface>…</brok:surface>
<brok:surface>…</brok:surface>
<brok:surface>…</brok:surface>
</brok:grid>
<brok:cluster gap="sm" justify="end">
<brok:button variant="outline">Cancel</brok:button>
<brok:button>Save</brok:button>
</brok:cluster>
</brok:stack>
</brok:container>
</brok:section>
Workspace activity
Readable copy wraps safely, including long translated strings and URLs.
Open issues
12
Merged this week
24
Review queue
36
Responsive contracts
- Use grids for collections that reflow; use stacks for fixed-direction forms and toolbars.
- Prefer intrinsic sizing and bounded max-width containers over fixed viewport widths.
- Move global sidebar navigation into a start-side sheet on narrow viewports.
- Keep tabular data readable with deliberate column priority, or let it scroll inside a bleed — never the page.
- Use logical start/end spacing so every frame mirrors in RTL.
<brok:prose>
<p>The table below is wider than the reading column, so it scrolls inside a bleed instead of the page.</p>
</brok:prose>
<brok:bleed :scroll="true">
<brok:table>…</brok:table>
</brok:bleed>
php artisan ui:build "responsive customer support dashboard"
php artisan ui:show pages/app-shell-bordered --skeleton --json