Layouts
Layouts allow you to wrap multiple pages in a consistent UI structure (like headers, footers, and sidebars). A layout is a regular .wire file that uses {$render children} to indicate where page content should be injected.
__layout__.wire is one of three convention files PyWire picks up automatically from your pages/ directory; see Error & Reconnect Pages for the others.
Creating a Layout
Section titled “Creating a Layout”<!-- A basic app shell layout -->---year = 2026---<nav> <a href="/">Home</a> <a href="/about">About</a> <a href="/contact">Contact</a></nav>
<main> {$render children}</main>
<footer>© {year} My App</footer>
<style scoped> nav { display: flex; gap: 1rem; padding: 1rem; border-bottom: 1px solid #e2e8f0; } main { padding: 2rem; } footer { padding: 1rem; color: #94a3b8; }</style>Layouts are full .wire components — they can have Python blocks, reactive state, scoped styles, and event handlers just like any other component.
Using Layouts
Section titled “Using Layouts”How you apply layouts depends on your routing strategy.
Path-Based Routing (default)
Section titled “Path-Based Routing (default)”Create a file named __layout__.wire in any directory inside your pages/ folder. It automatically wraps all pages in that directory and its subdirectories.
pages/├── __layout__.wire # Applies to ALL pages├── index.wire├── about.wire└── dashboard/ ├── __layout__.wire # Applies to all dashboard pages ├── index.wire └── settings.wireThe root __layout__.wire wraps every page. The dashboard/__layout__.wire wraps only pages inside dashboard/, and is itself nested inside the root layout.
Explicit Routing
Section titled “Explicit Routing”When using explicit routing (path_based_routing=False), layouts are applied with the !layout directive. The layout file can have any name and live anywhere in your project.
!path "/my-page"!layout "layouts/app_shell.wire"
<h1>This page uses the app shell layout</h1>Nested Layouts
Section titled “Nested Layouts”Layouts compose naturally. When a subdirectory has its own __layout__.wire, it nests inside the parent layout:
pages/__layout__.wire (outer):
<div class="app"> <header>My App</header> {$render children}</div>pages/dashboard/__layout__.wire (inner):
<div class="dashboard"> <aside>Dashboard Sidebar</aside> <div class="content"> {$render children} </div></div>Visiting /dashboard/settings renders: outer layout → inner layout → settings.wire content.
Layouts with State
Section titled “Layouts with State”Layouts can hold reactive state that persists as users navigate between pages wrapped by that layout. This is useful for things like sidebar toggle state, notification counts, or theme preferences.
---sidebar_open = wire(True)
def toggle_sidebar(): sidebar_open.value = not sidebar_open---<div class="app-shell"> <button @click={toggle_sidebar}>Toggle Sidebar</button> <aside $show={sidebar_open}> <nav> <a href="/">Home</a> <a href="/settings">Settings</a> </nav> </aside> <main> {$render children} </main></div>Layout with Context
Section titled “Layout with Context”Layouts are a natural place to provide context to all child pages using !provide:
!provide { 'THEME': theme }
---theme = wire("light")---<div class={f"app theme-{theme}"}> {$render children}</div>Any page or component can then inject the theme with !inject { theme: 'THEME' }. See Context & Injection for details.
Named Snippets and {$head}
Section titled “Named Snippets and {$head}”{$render children} is shorthand for the implicit children prop every component receives. Layouts can also define or receive named snippets for slots like titles or sidebars.
Page defines, layout renders:
!layout "layouts/app_shell.wire"
{$snippet title}About — My App{/snippet}
<h1>About</h1><p>Content goes here.</p><title>{$render title}My App{/render}</title><main> {$render children}</main>The paired form {$render title}...{/render} uses the body as a fallback when the page doesn’t provide that snippet.
Teleporting into <head>: Pages and components can contribute directly to the document <head> with {$head}...{/head}:
{$head} <title>{post.title}</title> <meta name="description" content={post.excerpt}>{/head}
<article>...</article>Contributions from every level of the render tree merge into the root <head>. The last <title> wins; non-title content retains author order.