Kida is Chirp's template engine. At build time it hands Chirp the structure of each template — its block names, its regions, and which context keys each block reads. Chirp reads that structure instead of hard-coding which blocks exist, which is how fragment rendering, out-of-band (OOB) regions, and layout contracts work with no framework-specific config.
You rarely touch any of this directly. To add an OOB region you write a
{% region name_oob(...) %}block in your layout and register its DOM target;
Chirp discovers the rest. This page explains that discovery so you can add your
own regions and reason about what gets rendered.
Add an OOB region
An OOB region is a layout block whose name ends in_oob. Chirp finds every
such block, renders it as an out-of-band swap on boosted navigation, and targets
it at a DOM element by id. Adding one is two steps.
- 1
Write the region in your layout
A
{% region name(...) %}block compiles to two things at once: a named block Chirp can render in isolation, and a callable you invoke from a shell slot. Name it with the_oobsuffix so discovery picks it up.{% region sidebar_oob(current_path="/") %} {{ sidebar(current_path=current_path) }} {% end %} - 2
Register its DOM target
Tell Chirp which element id the region replaces:
app.register_oob_region("sidebar_oob", target_id="sidebar-nav")If you skip registration, Chirp falls back to a convention: it strips the
_oobsuffix, sosidebar_oobtargets the element withid="sidebar". Register explicitly when the id does not match that convention.
That's the whole task. The
OOB registry page documents
register_oob_region options (swap, wrap, optional) and the fail-loud
policy in full.
ChirpUI registers three regions for you when you load its app shell —
breadcrumbs_oob, sidebar_oob, and title_oob, mapped to
chirpui-topbar-breadcrumbs, chirpui-sidebar-nav, and
chirpui-document-title. See App
Shells for regions in practice.
Why regions beat plain blocks
Kida's{% region %}compiles to BOTH a renderable block (for OOB updates) and
a callable (for{{ name(args) }}in shell slots). One definition serves both,
so you don't duplicate the markup:
- Renderable — Chirp renders the block on its own for an OOB swap.
- Callable — you invoke
{{ sidebar_oob(current_path=...) }}inside a slot. - Parameterized —
{% region sidebar_oob(current_path="/") %}makes the region self-contained; the same defaults apply whether it renders as a slot or as an OOB update.
Theexamples/chirpui/shell_oobreference layout defines and calls a region in
one place:
{% region breadcrumbs_oob(breadcrumb_items=[{"label":"Home","href":"/"}]) %}
{{ breadcrumbs(breadcrumb_items) }}
{% end %}
Source: examples/chirpui/shell_oob/pages/_layout.html.
{% call app_shell(brand="Settings Console", sidebar_collapsible=true, brand_boost=true) %}
{% slot topbar %}
{{ breadcrumbs_oob(breadcrumb_items=breadcrumb_items ?? [{"label":"Home","href":"/"}]) }}
{% end %}
Source: examples/chirpui/shell_oob/pages/_layout.html.
Migrate a block to a region
If your layout uses a plain{% block name_oob %}and duplicates the same markup
in a shell slot, collapse it to a single region.
{% block breadcrumbs_oob %}
{{ breadcrumbs(breadcrumb_items) }}
{% end %}
{% call app_shell() %}
{% slot topbar %}
{{ breadcrumbs(breadcrumb_items) }} {# duplicated #}
{% end %}
{% end %}
The OOB block and the slot render the same content from two copies. They drift.
{% region breadcrumbs_oob(breadcrumb_items=[{"label":"Home","href":"/"}]) %}
{{ breadcrumbs(breadcrumb_items) }}
{% end %}
{% call app_shell() %}
{% slot topbar %}
{{ breadcrumbs_oob(breadcrumb_items=breadcrumb_items ?? [{"label":"Home","href":"/"}]) }}
{% end %}
{% end %}
One definition; the slot calls the region. No duplication, no drift.
Then remove any empty_page_layoutblock overrides you added to suppress the
OOB block on full-page renders — Chirp already suppresses OOB output on full-page
responses through its block overrides, so those workarounds are no longer needed.
How discovery and validation work
Most readers stop above. The rest of this page is the mechanism for the curious.
The pipeline: template source to layout contract
Chirp never loads a template at request time to learn its shape. It reads Kida's structural metadata at build time and caches a contract.
- Template source → Kida parses and analyzes the AST.
- TemplateMetadata → blocks, regions, and per-block
depends_on/cache_scope. build_layout_contract()→ finds the*_oobblocks (preferring region-typed ones viaregions()), resolves each target id, and records itscache_scopeanddepends_on.- LayoutContract → cached per template; drives which OOB blocks render on boosted navigation.
For how this contract feeds the wider render plan, see the render plan.
When Chirp skips an OOB block
While building region updates, Chirp skips an OOB block in two cases:
- The block is site-scoped and has no dependencies (
cache_scope == "site"anddepends_onis empty). It's static — the same on every page — so there's nothing to swap. A site-scoped block that does read context still renders. - The block depends on
page_titlebutpage_titleisn't in the layout context, so there's nothing to render.
Both prevent emitting OOB chunks that would do no useful work.
Block validation in debug mode
Whenvalidate_blocks is on — Chirp turns it on automatically in debug=True
mode — Chirp checks that a requested block exists in the template's metadata
before rendering, instead of failing deep in the render. A missing block raises
chirp.errors.BlockNotFoundError:
from chirp.errors import BlockNotFoundError
raise BlockNotFoundError(template=view.template, block=view.block)
BlockNotFoundError multi-inherits from KeyError, so existing
except KeyError: handlers still catch it. The check reads meta.blocksfrom
the cached metadata — no runtime template load. The same fail-loud policy
governs OOB region misses; see
the OOB registry.
Metadata fields Chirp reads, and the adapter contract
Chirp consumes these fields from Kida'sTemplateMetadata:
| Field | Chirp use |
|---|---|
blocks |
Block existence checks and*_oobdiscovery |
regions() |
Prefer region-typed blocks for OOB discovery |
depends_on |
Skip an OOB block when its required context key is absent |
cache_scope |
Skip site-scoped OOB blocks with no dependencies |
Chirp reaches templates through theTemplateAdapterprotocol, whose
template_metadata(template) returns this structure or None. The Kida adapter
returns full metadata. An adapter that returnsNone(for example a Jinja2
adapter) opts out of discovery — Chirp can't confirm a block is renderable, so
it skips contract building rather than emitting phantom OOB targets.