Router: Layout
Declares a layout that wraps inner pages.
Use #[layout] without a path string with module routing. The enclosing module determines the URL prefix. A path starting with ./ extends the module path. An absolute path, such as #[layout("/settings")], chooses the URL independently of the module tree and requires separate registration.
module_router! registers module-derived handlers. For explicit paths, pass the function name to RouterBuilder::layout or use discover.
Handler signature
The function must be async and return a Result containing a View. It receives the inner content as slot: Slot<'_> and may also take cx: &Cx. The macro recognizes these parameters by name. They may appear in either order, and no other parameters are accepted.
Render slot where the inner content should appear. Wrap it in an error_boundary to show a custom view if it fails. See the error guide.
Examples
Module-derived path (in src/app/settings.rs under module_router!(), this wraps every page under /settings):
#[layout]
async fn settings_layout(slot: Slot<'_>) -> Result<impl View> {
Ok(view! {
<section>
<nav>"Settings nav"</nav>
(slot)
</section>
})
}
Explicit path:
use hearth::{Result, router::{Slot, href, layout}, view::{View, view}};
#[layout("/")]
async fn root_layout(slot: Slot<'_>) -> Result<impl View> {
Ok(view! {
<!DOCTYPE html>
<html>
<body>
<nav><a href=(href!(home))>"Home"</a></nav>
(slot)
</body>
</html>
})
}
Path below the module (in src/app/settings.rs under module_router!(), this wraps every page under /settings/admin):
#[layout("./admin")]
async fn admin_layout(slot: Slot<'_>) -> Result<impl View> {
Ok(view! {
<section>
<nav>"Admin nav"</nav>
(slot)
</section>
})
}
Nested layouts
When several layouts match a page, they nest from least specific (outermost) to most specific (innermost):
#[layout]
async fn root_layout(slot: Slot<'_>) -> Result<impl View> {
Ok(view! { <html><body>(slot)</body></html> })
}
mod settings {
use super::*;
#[layout]
async fn settings_layout(slot: Slot<'_>) -> Result<impl View> {
Ok(view! {
<div class="settings-shell">
<nav>"Settings nav"</nav>
(slot)
</div>
})
}
mod profile {
use super::*;
#[page]
async fn profile() -> Result<impl View> {
Ok(view! { <h1>"Profile"</h1> })
}
}
}
A request to /settings/profile renders root_layout > settings_layout > profile.
Layouts as components
A layout doubles as a component, taking a Slot as its slot property:
#[page]
async fn standalone() -> Result<impl View> {
Ok(view! {
root_layout(slot: Slot::new(view! { <p>"content"</p> }))
})
}