The Layout Pattern

Layouts wrap groups of routes. Keep the shared shell in layout.html and add layout.py only when that shell needs shared synchronous props or metadata.

Layout Anatomy

layout.html
<slot />
Child route output

Current Rules

  • Keep shared wrapper markup in layout.html.
  • Use layout.py only for shared synchronous props or metadata.
  • Child content is injected with <slot />.
  • Shared layout props are consumed with native Jinja such as {{ layout.* }}.
  • Shared metadata is consumed with native Jinja such as {{ metadata.* }}.
  • Caspian is HTML-first now, so the removed [[ ... ]] placeholders should not appear in new route templates.

Important runtime detail

layout() is synchronous in the installed runtime. Put async I/O in route page() functions or in route-owned @rpc() actions instead of awaiting inside layout.py.

layout.py

Use the Python companion for metadata defaults and shared layout props. The layout companion returns the rendered sibling template plus a props dict.

from casp.layout import Metadata, render_layout

metadata = Metadata(
    title="Docs Section | Caspian",
    description="Shared metadata for the docs subtree.",
)

def layout():
    return render_layout(__file__), 
        "shell_class": "docs-shell",
        "content_class": "docs-shell__content",
    

Return shape

Return render_layout(__file__), props so the sibling layout.html stays the authored wrapper while Python supplies data.

Metadata scope

Metadata defined here becomes the default for everything below this folder, unless a child layout or page overrides it.

layout.html

Keep the visible shell in the HTML file. Child routes render into the <slot /> outlet and shared values are read from native Jinja objects like layout and metadata.

<html>
  <head>
    <title>{{ metadata.title }}</title>
    <meta name="description" content="{{ metadata.description }}" />
  </head>
  <body class="{{ layout.shell_class }}">
    <aside>Docs navigation</aside>

    <main class="{{ layout.content_class }}" pp-reset-scroll="true">
      <slot />
    </main>
  </body>
</html>

Nested Layouts And Route Groups

Use nested layouts for shared shells in sections like dashboards, docs, and account areas. Use parenthesized route groups when the layout should organize code without adding a URL segment.

URL segment included
src/app/
  dashboard/
    layout.html
    layout.py
    index.html
    settings/
      index.html
URL segment omitted
src/app/
  (marketing)/
    layout.html
    pricing/
      index.html
    about/
      index.html

Scroll behavior

In grouped shells with separate sidebar and content scrolling, keep persistent shell scrollers unmarked and put pp-reset-scroll="true" on the content pane that should reset on child-route navigation.

Page-To-Layout Props

When a single page needs to influence a wrapping layout, return a 2-item tuple from page() instead of moving that concern into the layout for the whole subtree.

async def page():
    page_html = render_page(__file__, )

    return page_html, 
        "dashboard_body_class": "dashboard-shell dashboard-shell--reports"