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
Core Principles
-
1
Strict Precedence
If
layout.pyexists, 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
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
[[layout.*]].
[[metadata.*]].
The View
<!-- 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).
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.