Layouts
Any layout.html file wraps every page in its directory and in
every subdirectory below it.
Put <slot /> in the layout where the page content should
appear.
That is the whole rule. Here is a layout:
<!-- pages/layout.html -->
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<slot /> <!-- Page content goes here -->
</main>
<footer>
<p>© 2024</p>
</footer>
And a page that uses it without knowing it exists:
<!-- pages/about.html -->
<h1>About</h1>
<p>No nav, no footer. The layout supplies those.</p>
Visiting /about renders the nav, then this page's markup where
<slot /> was, then the footer.
Which pages a layout wraps
Every page below the layout file, however deep:
pages/
├── layout.html ← wraps everything below
├── index.html ← /
├── about.html ← /about
└── admin/
└── dashboard.html ← /admin/dashboard, same layout
A page uses the nearest layout.html above it, so adding one in
pages/admin/ takes over for that folder.
layout.html.
Layout.html is a component named Layout, and no
page will pick it up.
Nested layouts
A layout whose whole content sits inside <Layout> renders
inside its parent instead of replacing it:
<!-- pages/admin/layout.html -->
<Layout> <!-- Wraps in parent layout -->
<aside class="admin-sidebar">
<a href="/admin">Dashboard</a>
<a href="/admin/users">Users</a>
</aside>
<div class="admin-content">
<slot />
</div>
</Layout>
Now /admin/dashboard renders inside the admin sidebar, which
renders inside the root layout.
Leave the <Layout> wrapper off and the layout replaces its
parent instead. That suits sign-in screens, which usually want the page to
themselves:
<!-- pages/auth/layout.html -->
<!-- No <Layout> wrapper = replaces root layout -->
<div class="auth-page">
<div class="auth-card">
<Logo />
<slot />
</div>
</div>
The chain is resolved at build time and goes as deep as the directory tree does. Each
<Layout> takes the nearest layout above it;
<Layout src="../../layout.html"> names a different parent explicitly.
The wrapper has to be the whole file. Aside from
<link>, <style> and <script>,
<Layout> must be the entire top-level content. Nested inside an
element, or written twice, it inherits from nothing and would mount as a component no
build has - so it is refused by name.
A cycle - only reachable through src= - is a build error too, naming the
layouts that close the loop.
A layout can carry its own stylesheet
A <link rel="stylesheet"> written in a layout belongs to that layout. It
is lifted out of the markup at build time and travels in the layout's compiled record, so it
never appears inside the layout element: the sheet is loaded before the layout mounts, and
released when the layout leaves the chain and no other layout still claims the same
href.
<!-- pages/docs/layout.html -->
<link rel="stylesheet" href="/pages/docs/docs.css" />
<div class="docs-shell">
<Sidebar />
<slot />
</div>
href="{url}" is refused by name.
The lift happens before any binding could fill the value in, so the stylesheet would
silently never load.
Opting out
An empty layout.html means "no layout here". It breaks the chain
for its directory and everything below it:
pages/
├── layout.html ← root layout
└── embed/
├── layout.html ← empty file, no layout
└── widget.html ← renders bare
Layouts stay mounted
Navigating swaps the <slot /> content and nothing else.
The layout is never rebuilt, so its state, its animations and any playing media survive the move:
<!-- pages/layout.html -->
<script>
let theme = storage('light')
let sidebarOpen = true
</script>
<div class="{theme}">
<button onclick="sidebarOpen = !sidebarOpen">Toggle Sidebar</button>
<aside class:collapsed={!sidebarOpen}>
Navigation...
</aside>
<main>
<slot />
</main>
</div>
Move between pages and theme and sidebarOpen keep
their values.
Layouts are components
A layout is an ordinary component file with a special name, so it can hold state and use other components:
<!-- pages/layout.html -->
<script>
let theme = storage('system')
</script>
<div class="app theme-{theme}">
<Header />
<div class="main-content">
<slot />
</div>
<Footer />
</div>
Guarding a whole section
A <guard> in a layout covers every page under it, so the
check lives in one file:
<!-- pages/admin/layout.html -->
<Layout>
<guard access="admins" redirect="/login">
<AdminSidebar />
<slot />
</guard>
</Layout>