What a shell is
A shell is the root layout your page templates extend. It owns the
document root (<html>, <head>, <body>), loads htmx, and declares the
htmx-boost contract — the target id, swap mode, andhx-selectfilter
that govern how boosted navigation
flows into the page.
Pick exactly one shell per app. Your page templates, fragments, and feature modules render inside the shell's outlet.
The three shells
Chirp ships two; chirp-ui adds one more.
| Shell | Comes from | When to pick it |
|---|---|---|
chirp/layouts/boost.html |
core | htmx-boost SPA-style nav, no opinionated chrome |
chirp/layouts/shell.html |
core | Fragment-only apps (LLM/RAG playgrounds, dashboards, form-heavy UIs) — nohx-select, fragments flow exactly where hx-targetsays |
chirpui/app_shell_layout.html |
chirp-ui | Sidebar/topbar app chrome with breadcrumbs, shell actions, and OOB regions pre-wired |
The decisive question is whether your app needs persistent chrome — sidebar, topbar, breadcrumbs that survive boosted navigation:
- Yes →
app_shell_layout.html. See App Shells. - No, but you want SPA-style nav →
boost.html. See Boosted Navigation. - No, fragment swaps only →
shell.html.
Thehx-selectdistinction
The biggest hidden difference between the three is what the outlet element
(#main) sets for hx-select:
boost.html—hx-select="#page-content". Filters every response. Correct for boosted nav; silently discards fragment responses that don't contain#page-content.shell.html— nohx-select. Forms and fragment swaps land exactly wherehx-targetsays, with no filtering.app_shell_layout.html—hx-select="#page-content"on its<main>outlet. Same boost contract asboost.html, plus persistent chrome.
chirp check flags this mismatch via the select_inheritancerule (a
WARNING) when a mutating element may silently discard its response. See
the select_inheritance contract rule.
What is not a shell
Feature modules likechirp.docsship templates with their own visual chrome
(sidebar nav, search box, content area). They look shell-like, but they are
not shells — they render inside the outlet of whatever shell you extend.
A feature module's page templates do not establish<html>/<head>/<body>,
do not load htmx, and do not declare the boost contract. They render as the
content of a route handler (typicallyPage("chirp_docs/doc_page.html", "doc_content", doc=doc)) and compose into the shell's {% block content %}
slot.
Mountchirp.docs in an app that extends app_shell_layout.htmland the docs
sidebar lives inside the chirp-ui main outlet — not as a peer of the chirp-ui
sidebar. The shell stays in charge of the page frame.
Advanced: roll your own shell
Most apps extend one of the three shells above. If you need a custom root layout, replicate the boost contract on the outlet element:
<main id="main" tabindex="-1"
hx-boost="true" hx-target="#main" hx-swap="innerHTML" hx-select="#page-content">
<div id="page-content">
{% block content %}{% end %}
</div>
</main>
Sidebar links outside #main need their own hx-target="#main"and
hx-select="#page-content" since they don't inherit from the #mainelement.
Seeexamples/chirpui/kanban_shellfor a worked example.