Chirp Examples
August 8, 2026 · View on GitHub
Examples are organized by runtime layer so the support matrix is obvious:
examples/standalone: baseline Chirp withoutchirp_uiexamples/chirpui: app-shell and component-driven examples using ChirpUI
For the maintained app-owned template architecture, generate the executable
example directly with chirp new modular_app. Its pages/ tree owns layouts
and named full/fragment response blocks, while the explicit templates/ root
separates typed Kida components, product patterns, and rare private partials.
Generation does not change when chirp-ui is installed; request the compatibility
example deliberately with --with-chirpui.
Learning path
| Tier | Example | Teaches |
|---|---|---|
| 1 | standalone/hello, standalone/contacts | Routes, forms, Page / Fragment |
| 2 | chirpui/contacts_shell | App shell, _actions.py, _context.py |
| 3 | chirpui/lucky_cat | Signals, Suspense, SSE, OOB capstone |
Featured:
chirpui/lucky_cat— flagship ChirpUI demo (tier 3). Not your first Chirp app — complete tiers 1–2 first.
For a database-backed path across the maintained examples, follow the full-application journey.
AI curious
Branch off after tier 1 if you want to stream LLM tokens to the browser:
| Step | Example | Teaches |
|---|---|---|
| Start | standalone/llm_minimal | Simulated token streaming, TemplateStream vs EventStream, optional Ollama (chirp[ai]) |
| Next | standalone/ollama | Real local LLM, AgentRun, @app.tool(), live tool-call activity over SSE |
| In a shell | chirpui/llm_playground | The same streaming inside a ChirpUI app shell |
Streaming good-first issues (#454 and similar) should also:
- keep
app.check()/chirp checkfree of ERROR-severity issues - pair
TemplateStreamwith plain form POST (full page), nothx-targetswaps - pair
EventStreamwithFragmentscaffolding and parametricsse-connect - test htmx paths with
HX-Requestwhen forms usehx-* - add
@pytest.mark.issue(N)acceptance coverage when closing an issue
Which Bucket To Use
Use examples/standalone when you want to learn or validate:
- raw
Template/Fragment/EventStream - plain HTMX forms, fragments, and SSE
- standalone Chirp behavior without
delegation=True - data, auth, middleware, and API examples without ChirpUI dependencies
Use examples/chirpui when you want to learn or validate:
use_chirp_ui(app)- app-shell layouts and boosted navigation
delegation=Trueand shell-aware swaps- richer component-driven examples built on the newer UI layer
Running Examples
Run examples from the repo root so local src/chirp is always used:
# From the repo root:
source .venv/bin/activate
PYTHONPATH=src python examples/standalone/hello/app.py
For ChirpUI examples:
# From the repo root:
source .venv/bin/activate
PYTHONPATH=src python examples/chirpui/pages_shell/app.py
If you also need the local pounce checkout rather than an installed package:
PYTHONPATH=src:../pounce/src python examples/standalone/ollama/app.py
Pairings
Some examples intentionally come in pairs so you can compare the baseline and app-shell lanes:
examples/standalone/contactsandexamples/chirpui/contacts_shellexamples/chirpui/forum_shellfor a compact product-shaped ChirpUI shell with mounted pages, form contracts, OOB shell state, and JSON data islands; it is a regression fixture, not a full forum productexamples/standalone/kanbanandexamples/chirpui/kanban_shellexamples/standalone/islandsandexamples/chirpui/islands_shell
Notes
examples/AUDIT.mdremains a shared cross-example document.examples/conftest.pyremains shared so example tests can still reuse common fixtures.examples/__init__.pyremains at the root because the examples tree is still treated as one test/import surface.
Patterns
Lessons from building these examples — things that aren't bugs but require intentional decisions from the developer.
Mark third-party HTML with | safe
Kida auto-escapes all template output by default. This is correct — it prevents XSS. But when you're rendering pre-formatted HTML from an external source (API responses, CMS content, markdown output), you need to explicitly opt out:
{# Bad: <p> tags rendered as literal <p> text #}
<div class="comment-text">{{ comment.text }}</div>
{# Good: HTML rendered as intended #}
<div class="comment-text">{{ comment.text | safe }}</div>
The | safe filter accepts an optional reason= parameter for documenting why
the content is trusted:
{{ api_html | safe(reason="sanitized by HN API") }}
{{ rendered_md | safe(reason="generated by markdown parser") }}
Auto-escaping is a guardrail, not a sharp edge. The | safe call is your
declaration that you've thought about trust.
Use a unique ID for your htmx swap target
When chirp swaps fragments into the page, the hx-target selector must resolve
to exactly one element. A class like .container is a layout utility that may
appear in headers, footers, and sidebars. Use a unique ID instead:
{# Bad: ambiguous — multiple .container elements on the page #}
<a hx-get="/story/1" hx-target=".container">...</a>
{# Good: one unambiguous target #}
<div id="main" class="container">...</div>
<a hx-get="/story/1" hx-target="#main">...</a>
This also matters for View Transitions — view-transition-name must be unique
per page. Applying it to a class that matches multiple elements triggers a
browser warning and breaks the transition animation.
When using app_shell_layout.html, chirpui-transitions.css automatically
applies view-transition-name: page-content to #main and suppresses the
root transition so the shell stays frozen. No custom VT CSS needed for the
common case.
OOB + View Transitions: If #main (or similar) is a parent of elements
that receive OOB swaps (e.g. hx-swap-oob), three things can go wrong:
-
hx-targetinheritance —sse-connectinside#maininheritshx-target="#main". Fragments then swap into#main, wiping the whole list. Addhx-disinherit="hx-target hx-swap"onsse-connect. -
view-transition-nameon the parent — triggers full-page transition when OOB updates arrive. Scope it to elements that change only on full navigation (e.g..story-detail), not on parents of OOB targets. -
transition:trueon the swap target — htmx wraps swaps in the View Transitions API. OOB swaps to children can trigger this and cause the whole content area to flicker/disappear. Puttransition:trueonly on the links that trigger full navigation, not on the container (#main).
ID-based targeting is faster for the browser, unambiguous for htmx, and compatible with the CSS view-transition-name uniqueness requirement.
Running Tests
Each example has a test_app.py that verifies it works through the ASGI pipeline
using chirp's TestClient. No HTTP server required.
# All examples
pytest examples/
# One standalone example
pytest examples/standalone/hello/
# One chirpui example
pytest examples/chirpui/pages_shell/
What Each Example Exercises
The table below is generated from inventory.toml. It is the
authoritative catalog for lane, support status, learning tier, required extras,
network access, capabilities, README coverage, and test entrypoints. Run
python -m examples.inventory after editing the manifest.
- canonical examples are recommended reference implementations for their capability set.
- supporting examples are maintained, focused demonstrations rather than the default learning path.
- experimental examples prove provisional or advanced patterns and should not be treated as framework defaults.
Network describes the runnable app: none is offline, optional has an
offline default with an opt-in integration, and required needs an external or
local service. Extras names installable bengal-chirp[...] extras and is
checked against pyproject.toml. A missing README is recorded explicitly so it
cannot disappear from the catalog while its documentation debt remains visible.
| Example | Lane | Status | Tier | Extras | Network | Capabilities | README | Tests |
|---|---|---|---|---|---|---|---|---|
chirpui/contacts_shell | chirpui | canonical | 2 | sessions, ui | none | app-shell, chirpui, csrf, forms, fragments, pages, sessions | yes | contacts_shell |
chirpui/forum_shell | chirpui | canonical | 3 | ui | none | app-shell, chirpui, contracts, forms, oob, pages | yes | forum_shell |
chirpui/islands_shell | chirpui | supporting | 2 | ui | none | app-shell, chirpui, islands | yes | islands_shell |
chirpui/kanban_shell | chirpui | canonical | 3 | auth, sessions, ui | none | app-shell, auth, chirpui, csrf, forms, fragments, oob, sessions, sse, validation | yes | kanban_shell |
chirpui/llm_playground | chirpui | supporting | 3 | ai, markdown, ui | required | ai, app-shell, chirpui, fragments, sse, streaming | yes | llm_playground |
chirpui/lucky_cat | chirpui | canonical | 3 | auth, passkeys, sessions, ui | none | app-shell, auth, chirpui, csrf, data, forms, fragments, mutations, oob, pages, passkeys, reactive, sessions, sse, suspense, validation | yes | lucky_cat |
chirpui/pages_shell | chirpui | canonical | 2 | ui | none | app-shell, chirpui, pages, routing | yes | pages_shell |
chirpui/rag_demo | chirpui | canonical | 3 | ai, markdown, sessions, ui | required | ai, app-shell, chirpui, data, fragments, sessions, sse, streaming | yes | rag_demo |
chirpui/shell_oob | chirpui | canonical | 2 | ui | none | app-shell, chirpui, oob, pages | missing | shell_oob |
chirpui/sortable_reorder | chirpui | supporting | 2 | sessions, ui | none | app-shell, chirpui, csrf, fragments, mutations, sessions | yes | sortable_reorder |
standalone/accessibility | standalone | supporting | 2 | — | none | accessibility, forms, validation | yes | accessibility |
standalone/api | standalone | supporting | 1 | — | none | api, middleware, routing | yes | api |
standalone/auth | standalone | canonical | 2 | auth, sessions | none | auth, forms, pages, security, sessions | yes | auth |
standalone/chat | standalone | supporting | 2 | sessions | none | fragments, sessions, sse | yes | chat |
standalone/contacts | standalone | canonical | 1 | — | none | forms, fragments, mutations, oob, pages, validation | yes | contacts |
standalone/custom_middleware | standalone | supporting | 2 | — | none | middleware, routing | yes | custom_middleware |
standalone/dashboard | standalone | supporting | 2 | — | none | fragments, sse, suspense | yes | dashboard |
standalone/dashboard_live | standalone | supporting | 3 | — | none | data, fragments, sse, suspense | yes | dashboard_live |
standalone/devtools_htmx4 | standalone | supporting | 2 | — | required | contracts, fragments, islands, oob, view-transitions | yes | devtools_htmx4 |
standalone/docs_site | standalone | canonical | 2 | markdown | none | docs, routing, tools | missing | docs_site |
standalone/form_get | standalone | supporting | 1 | — | none | forms, routing | yes | form_get |
standalone/freeze_site | standalone | canonical | 2 | markdown | none | docs, freeze, pages | missing | freeze_site |
standalone/hackernews | standalone | supporting | 3 | testing | required | fragments, sse, view-transitions | yes | hackernews |
standalone/hello | standalone | canonical | 1 | — | none | routing | yes | hello |
standalone/htmx_managed | standalone | experimental | 2 | — | none | contracts, fragments | yes | htmx_managed |
standalone/islands | standalone | supporting | 2 | — | none | islands | yes | islands |
standalone/islands_swap | standalone | experimental | 2 | — | none | fragments, islands | yes | islands_swap |
standalone/kanban | standalone | canonical | 3 | auth, sessions | none | auth, csrf, forms, fragments, oob, sessions, sse, validation | yes | kanban |
standalone/llm_minimal | standalone | canonical | 1 | — | optional | ai, fragments, sse, streaming | yes | llm_minimal |
standalone/llm_streaming_kida | standalone | supporting | 2 | — | optional | ai, streaming | yes | llm_streaming_kida |
standalone/milo_mcp_apps | standalone | experimental | 3 | — | none | contracts, pages, tools | yes | milo_mcp_apps |
standalone/mutation_result | standalone | supporting | 2 | — | none | fragments, mutations, pages | yes | mutation_result |
standalone/nojs_floor | standalone | canonical | 2 | — | none | forms, fragments, mutations, no-js, pages, validation | yes | nojs_floor |
standalone/ollama | standalone | canonical | 3 | ai, markdown | required | ai, fragments, sse, streaming, tools | yes | ollama |
standalone/oob_layout_chain | standalone | experimental | 3 | — | none | contracts, oob, pages | yes | oob_layout_chain |
standalone/optimistic_apply | standalone | experimental | 3 | — | none | fragments, islands, mutations | yes | optimistic_apply |
standalone/orrery | standalone | canonical | 3 | sessions, skill | none | contracts, csrf, fragments, pages, security, sessions, sse, tools | yes | orrery |
standalone/passkeys_minimal | standalone | supporting | 3 | auth, passkeys, sessions | none | auth, csrf, forms, passkeys, security, sessions | yes | passkeys_minimal |
standalone/pokedex | standalone | supporting | 2 | — | none | api, data, middleware, pages | yes | pokedex |
standalone/production | standalone | canonical | 3 | sessions | none | csrf, forms, security, sessions | yes | production |
standalone/query_search | standalone | canonical | 3 | — | optional | forms, fragments, no-js, pages, query, routing, validation | yes | query_search |
standalone/reactive_tasks | standalone | canonical | 3 | — | none | fragments, reactive, sse, validation | yes | reactive_tasks |
standalone/returns_gallery | standalone | canonical | 2 | — | none | fragments, mutations, oob, pages, sse, streaming, suspense, validation | yes | returns_gallery |
standalone/search | standalone | supporting | 1 | — | none | forms, pages, routing | yes | search |
standalone/shapes_workspaces | standalone | experimental | 3 | — | none | contracts, data, forms, pages | yes | shapes_workspaces |
standalone/signup | standalone | supporting | 2 | sessions | none | csrf, forms, sessions, validation | yes | signup |
standalone/sse | standalone | canonical | 2 | — | none | fragments, sse | yes | sse |
standalone/sse_reconnect | standalone | supporting | 3 | — | none | fragments, sse | yes | sse_reconnect |
standalone/static_site | standalone | supporting | 2 | — | none | sse, static-files | yes | static_site |
standalone/streaming | standalone | canonical | 2 | — | none | streaming | yes | streaming |
standalone/survey | standalone | supporting | 2 | — | none | forms, validation | yes | survey |
standalone/suspense_dashboard | standalone | supporting | 2 | — | none | suspense | yes | suspense_dashboard |
standalone/theming | standalone | supporting | 1 | — | none | pages | yes | theming |
standalone/todo | standalone | canonical | 2 | sessions | none | csrf, data, forms, fragments, mutations, no-js, pages, security, sessions, validation | yes | todo |
standalone/tools | standalone | supporting | 2 | — | none | fragments, sse, tools | yes | tools |
standalone/tools_hitl | standalone | experimental | 3 | ai, sessions | none | ai, csrf, fragments, sessions, sse, tools | yes | tools_hitl |
standalone/upload | standalone | supporting | 2 | forms, sessions | none | csrf, forms, sessions, uploads, validation | yes | upload |
standalone/webmcp_form | standalone | experimental | 2 | sessions | none | csrf, forms, fragments, mutations, sessions, validation, webmcp | yes | webmcp_form |
standalone/wizard | standalone | supporting | 2 | sessions | none | forms, pages, sessions, validation | yes | wizard |