Layouts & Includes
Introduction
A real site is rarely a single template. Layouts wrap a page in the surrounding chrome - <head>, navigation, footer, scripts. Includes pull a shared chunk of markup into a page from one place so you can update it once and have every caller follow. Ree handles both with two tags: {#layout} and {#include}.
Layouts
Place {#layout('name') } at the top of a template to wrap its output inside a layout file. The template's rendered HTML is exposed to the layout as props.body:
{#layout('layout') }
<div class="p-4">
<h1>{= props.title }</h1>
<p>{= props.description }</p>
</div>
Inside the layout file, render the body wherever the page should be inserted:
<!DOCTYPE html>
<html>
<head>
<title>{= props.title }</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<nav>...</nav>
<main>{~ props.body }</main>
<footer>...</footer>
</body>
</html>
Use {~ } to inject the body - the body is HTML you generated and you do not want it escaped.
Only one {#layout} per template is supported, and it must appear at the top. The layout itself can extend another layout the same way, so you can build nested wrappers.
Passing Extra Data to the Layout
The layout receives everything in props from the page automatically - props.title, anything else you passed. To override a value just for the layout or to add layout-specific data, pass a second argument to {#layout}:
{#layout('layout', { title: "Override Title", show_sidebar: false }) }
The object you pass is merged on top of props, so any keys you specify here win over what the page received.
Includes
{#include('path') } pulls another template inline. The included template shares the current props by default, and you can pass extra values as a second argument:
{#include('partials/header') } {#include('./sidebar', { active: props.current_section }) }
Use {#include} when:
- The chunk doesn't need to be configured with props (a static header or footer).
- The chunk lives close to the page that uses it - a partial inside a template folder.
- You'd reach for it across feature boundaries and want a stable, addressable path.
For chunks that take props and live in a shared catalogue, Components (ReeTags) are the cleaner option - they're built specifically for that case.
Path Resolution
{#include} and {#layout} accept several path styles. The engine resolves each style consistently across both tags.
| Prefix | Resolves From | Example |
|---|---|---|
$components/ | Project root components/ | $components/button |
$routes/ | Project root routes/ | $routes/home/hero |
$lib/ | Project root lib/ | $lib/flash |
./ or ../ | Relative to the current template | ./sidebar, ../shared/footer |
/name | Views root, absolute | /layouts/base |
name | Views root, implicit | layouts/base |
Without an extension, the engine appends .ree and treats the target as a template. With an explicit extension other than .ree - .css, .svg, .txt, anything else - the file is loaded and injected as raw text, unescaped:
<style>
{#include('./styles.css') }
</style>
This is occasionally useful for inlining a small stylesheet or an SVG icon. The file's content is dropped in verbatim, so don't include user-controlled paths here.
Path Security
Path traversal outside the resolved base directory is blocked at compile time. An include like {#include('../../../../etc/passwd') } raises an error rather than reading a file outside the allowed directories. Alias paths ($lib/, $routes/, $components/) are similarly anchored to their respective project-root directories.
Data Flow Into Includes
Includes receive a merged copy of the current props plus whatever you pass as the second argument. The second argument wins on conflict:
<!-- parent template -->
{{ const items = ["alpha", "beta", "gamma"] }} {#include('./list', { items, show_index: true }) }
<!-- routes/list.ree -->
<ul>
{#each props.items as item, i }
<li>{#if props.show_index }<strong>{= i + 1 }.</strong>{/if} {= item }</li>
{/each}
</ul>
Anything the parent had - props.title, translations - is still available inside the include. The second argument just adds (or overrides) on top.
When to Use Each
A rough decision tree:
{#layout}- once per page, at the top, for the surrounding HTML shell.{#include}- for a chunk of markup you want to factor out of a page, with or without extra data.- ReeTags (
<component-name>) - for reusable, prop-driven pieces shared across pages (form inputs, banners, cards). Covered in Components.