Layouts
Layouts give pages a shared skeleton: the html head, navigation, theme, and footer live in a layout once, not in every page.
How layouts work#
A layout.py file exports a layout() function
that receives the rendered children and returns the wrapping HTML.
The root layout lives at app/layout.py and wraps every
page in the project.
def layout(children, request):
...
The children argument is the page, already wrapped by any
deeper layouts. request is provided when the request is
available; the build step may call layouts without a request, so the
argument can be omitted from the signature when you do not need it.
The root layout#
The scaffold generates app/layout.py with the document skeleton:
# app/layout.py
from koyoapp.html import body, head, html, link, meta, script
from koyoapp.meta import metadata_tags
from components.site import site_footer
def layout(children, request):
return html(lang="en")[
head()[
meta(charset="utf-8"),
meta(name="viewport", content="width=device-width, initial-scale=1"),
meta(name="theme-color", content="#1800ad"),
link(rel="stylesheet", href="/styles/koyo.css"),
script(src="/htmx.min.js"),
metadata_tags(),
],
body(class_="bg-white antialiased")[
children,
site_footer(),
],
]
The root layout loads the compiled stylesheet, the vendored htmx
script, and renders the site footer around children.
metadata_tags() turns the metadata
dict into real head tags.
Nested layouts#
A layout.py in any folder wraps every page below it in
the tree, composed inside the root layout. For example, a blog layout:
# app/blog/layout.py
from koyoapp.html import aside, div, h2
def layout(children):
return div(class_="grid grid-cols-1 lg:grid-cols-4 gap-8")[
aside(class_="lg:col-span-1")[
h2("Blog index"),
],
div(class_="lg:col-span-3")[
children,
],
]
Here app/blog/page.py and app/blog/[slug]/page.py
both render inside the blog sidebar, which itself renders inside the
root layout. Nesting orders from the deepest layout outward: page,
then folder layouts, then the root.
Using the request#
A layout can accept request to render request-specific
content, for example the route that was visited:
def layout(children, request):
return html(lang="en")[
body[
main[
children,
p(f"Served via {request.method}"),
],
],
]
Guard any request usage, because during koyoapp build
the request is not available. Components used in layouts should treat
request as optional and fall back gracefully, the way
use_state returns its initial value without a live
request.