The Layout Pattern

Define your application's shell once. Use layout.html for shared UI and layout.py to inject dynamic context and control SEO metadata.

Anatomy of a View

layout.html
[[ children ]]
index.html (Page)

Core Principles

  • 1

    Strict Precedence

    If layout.py exists, it is the sole authority. It must explicitly return the rendered HTML template.

  • 2

    Props Bubbling

    Properties flow upwards. A nested layout (e.g. /dashboard/layout.py) can override properties defined in the root layout.

  • 3

    Nested Wrapping

    Layouts render from the inside out. The page renders inside the leaf layout, which renders inside the parent layout, up to the root.

The Controller

Python
from casp.layout import render_layout, Metadata

# 1. Define SEO Metadata (Bubbles up to <head>)
Metadata(
    title="My App",
    description="The best app ever"
)

# 2. Define Logic
# Accepts 'context' dictionary (contains request, user, etc.)
def layout(context: dict):
    # Prepare visual props for layout.html
    props = {
        "user_email": context.get("user", {}).get("email"),
        "theme": "dark"
    }

    # 3. Return Tuple: (HTML Content, Props)
    return render_layout(__file__), props
Return Types
tuple(str, dict) Returns the rendered template string plus a dictionary of props available in HTML via [[layout.*]].
Metadata(...) Defines SEO tags. Accessed via [[metadata.*]].

The View

HTML
<!-- Access props via layout.* -->
<html>
  <head>
    <title>[[ metadata.title ]]</title>
    <meta name="description" content="[[ metadata.description ]]" />
  </head>
  <body class="[[ layout.theme ]]">
    
    <nav>
        Hello, [[ layout.user_email ]]
    </nav>

    <!-- The content injection point (CRITICAL) -->
    [[ children | safe ]]

  </body>
</html>

Head Injection

Use the inject_html utility to dynamically append scripts, styles, or meta tags to the document head from within your Python logic.

from casp.layout import inject_html

def layout(context):
    # Inject analytics.html into <head>
    inject_html("analytics.html", target="head", id="G-12345")
    
    return render_layout(__file__), {}

Route Groups & Multiple Root Layouts

Sometimes you need completely different layouts for different parts of your app (e.g., a Marketing landing page vs. an App Dashboard). You can use Route Groups by wrapping folder names in parenthesis (name).

Project Structure
src/app
(marketing)
layout.html Marketing UI
/customers/index.html
(dashboard)
layout.html Admin UI
index.html

How it works

Folders in parenthesis are omitted from the URL.

  • app/(marketing)/customers/index.html/
  • app/(dashboard)/index.html/dashboard *

* Assuming dashboard is defined inside the group or handled via index precedence.

Benefit

This allows you to define a Marketing Layout (Navbar, Footer, Hero) that is completely different from your Dashboard Layout (Sidebar, Header, Graphs) without conditional logic spaghetti.