Overview
Chirp templates are Kida templates. You compose a page from four constructs:
{% extends %}— inherit a root layout (shell).{% block %}— fill or extend an overridable slot the layout left open.{% include %}— pull in a partial (a header, footer, or card).{% call %}— invoke a parameterized macro defined with{% def %}.
This page shows the idiomatic pattern for each and how page content nests inside a shell.
When to reach for each construct
| Construct | Use for |
|---|---|
{% extends %} |
The root layout for a page. One per template. |
{% block %} |
Overridable sections. A child template fills a block, or extends it with{{ super() }}. |
{% include %} |
Reusable partials (headers, footers, cards). No parameters. |
{% call %} |
Parameterized components. Pair with{% def %}to define the macro. |
Blocks define slots; includes pull in full partials;call/defare
parameterized components.
Extend a shell
Whatever shell you picked, the extension shape is the same — name the layout, then fill its blocks:
{% extends "chirp/layouts/boost.html" %} {# or shell.html, or chirpui/app_shell_layout.html #}
{% block title %}My App{% end %}
{% block content %}
<p>Page content goes here.</p>
{% end %}
{% block body_after %}
<script>/* app-specific JS */</script>
{% end %}
Every root layout exposes title, head, content, sse_scope, and
body_after (plus lang). Beyond that shared set, the layouts differ: switching
from one shell to another is not always a no-op.
| Shell | Blocks beyond the shared set |
|---|---|
boost.html |
body_before, head_style |
shell.html |
scripts, shell |
chirp-uiapp_shell_layout.html |
brand, sidebar, topbar_leading, topbar_center, topbar_end, context_rail, head_extra, page_scripts |
If a page fillsbody_before on boost.html and you move it to shell.html,
that block silently does nothing —shell.html has no body_before. Check the
target shell's blocks before migrating. For the chirp-ui layout blocks
(brand, sidebar, and the topbar slots), see
App Shells.
page_root and page_content are not app_shell_layout.htmlblocks. They
are page-composition blocks your page defines, injected into the layout's
contentblock — not layout overrides. A template that
{% extends "chirpui/app_shell_layout.html" %} and writes {% block page_root %}
gets a silent no-op, the same trap asbody_beforeabove.
Override and extend blocks
A child template overrides a block by redefining it. To build on the parent's
content instead of replacing it, call{{ super() }}:
{% extends "base.html" %}
{% block content %}
{{ super() }}
<p>Additional content after the parent block.</p>
{% end %}
{{ super() }}renders the parent block's content in place. Omit it to replace
the block entirely.
Mounted pages compose, they do not inherit
Filesystem pages are
different from the{% extends %} flow above. A page.htmldoes not
{% extends %} its sibling _layout.html. Instead Chirp composes them: page
HTML is injected into the layout's{% block content %}via the internal
render_with_blockspass.
Because there is no inheritance link, a page template cannot override a
layout block such aspage_scripts or head_extra— those slots belong to
templates that{% extends %}the layout directly. If a mounted page needs an
inline<script>, place it inside the content region (inside page_rootor
page_content), not in a sibling layout block.
Advanced
SSE swap-target structure (outer vs inner)
When a block is the target of an SSE swap, split the markup into two elements so updates do not double up padding or borders:
- Outer element — the
sse-swaptarget. Holds padding, border, and layout. It stays in the DOM; itsinnerHTMLis replaced. - Inner element — the fragment block content. Carries no duplicate padding or border.
<!-- Outer: swap target, has padding/border; hx-target="this" when sse-connect has hx-disinherit -->
<div class="answer" sse-swap="answer" hx-target="this">
<!-- Inner: the fragment renders this; no extra padding -->
<div class="answer-body" data-copy-text="...">
<div class="answer-content prose">...</div>
<button class="copy-btn">Copy</button>
</div>
</div>
Avoid nesting two elements with the same padding or border — it causes double
spacing. Keep.copy-btn in normal flow (no position: absolute) so it stays
with its answer. For the full SSE fragment-structure playbook, see
SSE patterns.