Classes
Template
5
▼
Render a full kida template.
Usage::
return Template("page.html", title="Home", items=items)
Template
5
▼
Render a full kida template.
Usage::
return Template("page.html", title="Home", items=items)
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
context |
dict[str, Any]
|
— |
Methods
name
0
str
▼
Deprecated alias for ``template_name``.
property
name
0
str
▼
def name(self) -> str
Returns
str
inline
2
InlineTemplate
▼
Create a template from a string. For prototyping only.
Usage::
return Te…
staticmethod
inline
2
InlineTemplate
▼
def inline(source: str, /, **context: Any) -> InlineTemplate
Create a template from a string. For prototyping only.
Usage::
return Template.inline("<h1>{{ title }}</h1>", title="Hello")
Parameters
| Name | Type | Description |
|---|---|---|
source |
— |
|
**context |
— |
Returns
InlineTemplate
Internal Methods 1 ▼
__init__
2
▼
__init__
2
▼
def __init__(self, template_name: str, /, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
**context |
— |
InlineTemplate
3
▼
A template rendered from a string source. For prototyping.
Separate type so the content negotiati…
InlineTemplate
3
▼
A template rendered from a string source. For prototyping.
Separate type so the content negotiation layer can distinguish it
from file-based templates, andapp.check()can warn about
inline templates in production code.
Attributes
| Name | Type | Description |
|---|---|---|
source |
str
|
— |
context |
dict[str, Any]
|
— |
Methods
Internal Methods 1 ▼
__init__
2
▼
__init__
2
▼
def __init__(self, source: str, /, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
source |
— |
|
**context |
— |
Fragment
6
▼
Render a named block from a kida template.
The *target* field controls how the fragment is deliver…
Fragment
6
▼
Render a named block from a kida template.
The target field controls how the fragment is delivered:
- OOB responses: target specifies the DOM element ID for the
out-of-band swap. If target is
None(the default), the block name is used as the target ID. - SSE streams: target becomes the SSE event name. Templates
use
sse-swap="{target}"to receive the fragment. If target isNone, the event name defaults to htmx's"message"channel.
Usage::
return Fragment("search.html", "results_list", results=results)
With explicit OOB target::
Fragment("cart.html", "counter", target="cart-counter", count=5)
With explicit SSE event name::
yield Fragment("dashboard.html", "stats_panel",
target="stats-update", stats=stats)
# Client: <div sse-swap="stats-update">
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
block_name |
str
|
— |
target |
str | None
|
— |
swap |
str | None
|
— |
context |
dict[str, Any]
|
— |
Methods
Internal Methods 1 ▼
__init__
5
▼
__init__
5
▼
def __init__(self, template_name: str, block_name: str, /, *, target: str | None = None, swap: str | None = None, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
block_name |
— |
|
target |
— |
Default:None
|
swap |
— |
Default:None
|
**context |
— |
Page
8
▼
Render a full template or a request-aware page fragment.
Combines Template and Fragment semantics.…
Page
8
▼
Render a full template or a request-aware page fragment.
Combines Template and Fragment semantics. The content negotiation layer inspects the incoming request headers and renders:
- Full template for normal browser navigations and htmx history-restore requests.
- Named fragment block for narrow htmx fragment requests
(
HX-RequestwithoutHX-History-Restore-Request). - Page block for boosted navigations when a page needs a wider, fragment-safe root than the narrow fragment block.
This eliminates the manualif request.is_htmxboilerplate
that every htmx-reachable route would otherwise need.
Usage::
return Page("hackernews.html", "story_list",
stories=stories, page="list")
With an explicit page-level block for boosted navigation::
return Page(
"dashboard.html",
"results_panel",
page_block_name="page_root",
stats=stats,
)
For page-directory/app-shell templates that follow Chirp's conventional
page_root / page_contentblocks::
return Page.mounted("dashboard/page.html", stats=stats)
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
block_name |
str
|
— |
page_block_name |
str | None
|
— |
context |
dict[str, Any]
|
— |
Methods
name
0
str
▼
Deprecated alias for ``template_name``.
property
name
0
str
▼
def name(self) -> str
Returns
str
effective_page_block_name
0
str
▼
Block used when a full page fragment root is required.
property
effective_page_block_name
0
str
▼
def effective_page_block_name(self) -> str
Returns
str
mounted
4
Page
▼
Create a Page for mounted page-directory/app-shell templates.
staticmethod
mounted
4
Page
▼
def mounted(template_name: str, /, *, block_name: str = 'page_content', page_block_name: str = 'page_root', **context: Any) -> Page
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
block_name |
— |
Default:'page_content'
|
page_block_name |
— |
Default:'page_root'
|
**context |
— |
Returns
Page
Internal Methods 1 ▼
__init__
4
▼
__init__
4
▼
def __init__(self, template_name: str, block_name: str | None = None, /, *, page_block_name: str | None = None, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
block_name |
— |
Default:None
|
page_block_name |
— |
Default:None
|
**context |
— |
Action
3
▼
Represent a side-effect endpoint that should not swap response HTML.
Defaults to ``204 No Content`…
Action
3
▼
Represent a side-effect endpoint that should not swap response HTML.
Defaults to204 No Contentso htmx receives a successful response
without replacing any target content. Optional htmx response headers can
be attached for client-side behavior.
Usage::
return Action()
return Action(trigger="saved")
return Action(refresh=True)
Attributes
| Name | Type | Description |
|---|---|---|
status |
int
|
— |
trigger |
str | dict[str, Any] | None
|
— |
refresh |
bool
|
— |
MutationResult
1
▼
Mutation success with progressive enhancement.
Also exported as ``FormAction`` — both names resolv…
MutationResult
1
▼
Mutation success with progressive enhancement.
Also exported asFormAction— both names resolve to the same class.
UseFormActionwhen the mutation is a form submission and
MutationResultfor non-form mutations (API endpoints, htmx-driven
actions); the behavior is identical.
Auto-negotiates htmx vs non-htmx responses for any mutation (POST, PUT, PATCH, DELETE):
- htmx + fragments: renders fragments (OOB-style) + optional
HX-Triggerheader. No redirect. - htmx + no fragments:
HX-RedirecttoredirectURL (client-side full redirect). - non-htmx: 303 redirect to
redirectURL.
Usage (form submission)::
return MutationResult("/contacts")
With fragments for htmx (non-htmx still gets a redirect)::
return MutationResult(
"/contacts",
Fragment("contacts.html", "table", contacts=contacts),
Fragment("contacts.html", "count", target="count", count=len(contacts)),
trigger="contactAdded",
)
DELETE with confirmation::
return MutationResult(
"/items",
Fragment("items.html", "list", items=remaining),
trigger="itemDeleted",
)
Methods
Internal Methods 1 ▼
__init__
4
▼
__init__
4
▼
def __init__(self, redirect: str, *fragments: Fragment, trigger: str | None = None, status: int = 303) -> None
Parameters
| Name | Type | Description |
|---|---|---|
redirect |
— |
|
*fragments |
— |
|
trigger |
— |
Default:None
|
status |
— |
Default:303
|
ValidationError
5
▼
Return a form fragment with 422 status for htmx validation.
Bundles the most common htmx form patt…
ValidationError
5
▼
Return a form fragment with 422 status for htmx validation.
Bundles the most common htmx form pattern: validate server-side, re-render the form fragment with errors on failure, return 422 so htmx knows to swap the error content.
The negotiation layer renders this as aFragmentwith status
422. If retarget is set, theHX-Retargetresponse header is
added so htmx swaps errors into a different element than the
original trigger.
Usage::
result = validate(form, rules)
if not result:
return ValidationError("form.html", "form_body",
errors=result.errors, form=form)
With retarget::
return ValidationError("form.html", "form_errors",
retarget="#error-banner",
errors=result.errors)
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
block_name |
str
|
— |
retarget |
str | None
|
— |
context |
dict[str, Any]
|
— |
Methods
Internal Methods 1 ▼
__init__
4
▼
__init__
4
▼
def __init__(self, template_name: str, block_name: str, /, *, retarget: str | None = None, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
block_name |
— |
|
retarget |
— |
Default:None
|
**context |
— |
Stream
3
▼
Render a kida template with progressive streaming.
**When to use:** All data is known upfront (or …
Stream
3
▼
Render a kida template with progressive streaming.
When to use: All data is known upfront (or resolves quickly), but the template is large and you want the browser to start painting before the full HTML is ready. Context awaitables resolve concurrently before streaming begins.
Not this — use TemplateStream when the template itself consumes an
async iterator ({% async for %}, {{ await }}).
Not this — use Suspense when you want a shell/skeleton rendered immediately while slow data loads in the background.
Usage::
return Stream("dashboard.html",
header=site_header(),
stats=await load_stats(),
feed=await load_feed(),
)
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
context |
dict[str, Any]
|
— |
Methods
Internal Methods 1 ▼
__init__
2
▼
__init__
2
▼
def __init__(self, template_name: str, /, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
**context |
— |
TemplateStream
3
▼
Render a template with Kida's render_stream_async.
**When to use:** The template itself consumes a…
TemplateStream
3
▼
Render a template with Kida's render_stream_async.
When to use: The template itself consumes an async iterator via
{% async for %} or {{ await }}. HTML chunks stream to the
browser as the iterator yields. O(n) — one pass, not re-render per
item. Ideal for LLM token streaming and long async feeds.
Not this — use Stream when all data resolves upfront and you just want chunked transfer of a large template.
Not this — use Suspense when you want a shell rendered first, then slow sections filled in as out-of-band swaps.
Usage::
return TemplateStream("chat.html",
stream=llm.stream(prompt),
prompt=prompt,
)
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
context |
dict[str, Any]
|
— |
Methods
Internal Methods 1 ▼
__init__
2
▼
__init__
2
▼
def __init__(self, template_name: str, /, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
**context |
— |
Suspense
6
▼
Render a page shell immediately, then fill in deferred blocks via OOB.
**When to use:** The page h…
Suspense
6
▼
Render a page shell immediately, then fill in deferred blocks via OOB.
When to use: The page has slow data sources (DB queries, API calls) and you want the user to see the page shell/skeleton instantly. Deferred blocks stream in as out-of-band swaps when their data resolves. Best for dashboards, detail pages with multiple independent data sources.
Not this — use Stream when all data resolves quickly and you just want chunked transfer of a large template.
Not this — use TemplateStream when the template consumes an async
iterator inline ({% async for %}).
Like React's<Suspense>but server-rendered. Context values that
are awaitables are deferred: the shell renders with those keys
set to theDEFERREDsentinel (showing skeleton/fallback content),
then each block is re-rendered with real data and streamed as an OOB
swap chunk.
The shell also sets__chirp_defer_pending__(see
CHIRP_DEFER_PENDING_KEY in chirp.templating.suspense) to a
frozensetof deferred context key names; deferred block re-renders
use an empty frozenset. Do not use that name for your own context keys.
Templates: Use{% if stats is deferred %}for skeleton vs loaded.
Bare{% if stats %} raises TypeErrorto prevent the common
footgun where empty results ([], 0, "") keep skeletons
visible after resolution.
For htmx navigations, blocks arrive ashx-swap-oobelements.
For initial page loads,<template> + inline <script>pairs
handle the swap without any framework.
Usage::
return Suspense("dashboard.html",
header=site_header(), # sync — in the shell
stats=load_stats(), # awaitable — deferred
feed=load_feed(), # awaitable — deferred
)
Template (skeleton vs loaded — useis deferred, not {% if stats %})::
{% block stats %}
{% if stats is deferred %}
<div class="skeleton">Loading stats...</div>
{% else %}
{% for s in stats %}...{% end %}
{% end %}
{% end %}
Block-to-DOM mapping defaults to block name = element ID. Override with defer_map::
Suspense("page.html", defer_map={"stats": "stats-panel"}, ...)
When static analysis misses blocks (e.g. deferred values passed through macro calls), list them explicitly with defer_blocks::
Suspense("page.html",
defer_blocks=("hero_stars", "footer_stars"),
stars=fetch_stars(),
)
If a deferred value fails after the shell is sent, the skeleton is
replaced with an error indicator. Use error_block to render a
custom fallback from the globalsuspense_error_template
(configured viaAppConfig). When omitted, the error_block
fromAppConfig.suspense_error_blockis used. If no error
template is configured, a hardcoded default is used::
Suspense("page.html",
error_block="custom_fallback",
stats=load_stats(),
)
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
context |
dict[str, Any]
|
— |
defer_map |
dict[str, str]
|
— |
defer_blocks |
tuple[str, ...] | None
|
— |
error_block |
str | None
|
— |
Methods
Internal Methods 1 ▼
__init__
5
▼
__init__
5
▼
def __init__(self, template_name: str, /, *, defer_map: dict[str, str] | None = None, defer_blocks: tuple[str, ...] | None = None, error_block: str | None = None, **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
defer_map |
— |
Default:None
|
defer_blocks |
— |
Default:None
|
error_block |
— |
Default:None
|
**context |
— |
LayoutSuspense
5
▼
Suspense with layout chain — used when Suspense is returned from mount_pages.
Carries layout metad…
LayoutSuspense
5
▼
Suspense with layout chain — used when Suspense is returned from mount_pages.
Carries layout metadata so the first chunk is wrapped in the layout shell (head, CSS, sidebar, etc.). OOB chunks target block IDs inside the page.
Attributes
| Name | Type | Description |
|---|---|---|
suspense |
Suspense
|
— |
layout_chain |
Any
|
— |
context |
dict[str, Any]
|
— |
request |
Any
|
— |
Methods
Internal Methods 1 ▼
__init__
4
▼
__init__
4
▼
def __init__(self, suspense: Suspense, layout_chain: Any, /, *, context: dict[str, Any] | None = None, request: Any = None) -> None
Parameters
| Name | Type | Description |
|---|---|---|
suspense |
— |
|
layout_chain |
— |
|
context |
— |
Default:None
|
request |
— |
Default:None
|
LayoutPage
9
▼
Render a page within a filesystem-based layout chain.
Used by ``mount_pages()`` routes. The negot…
LayoutPage
9
▼
Render a page within a filesystem-based layout chain.
Used bymount_pages()routes. The negotiation layer composes
the layout chain at the correct depth based onHX-Target:
- Full page load: render all layouts nested around the page block
- Boosted navigation: render from the targeted layout down using the page block
- Fragment request: render just the fragment block
The layout_chain and context_providers are set by the pages discovery system — handlers never construct this directly.
Usage (internal — set by the pages framework)::
return LayoutPage(
"page.html",
"content",
page_block_name="page_root",
layout_chain=chain,
context_providers=providers,
title="Home",
)
Attributes
| Name | Type | Description |
|---|---|---|
template_name |
str
|
— |
block_name |
str
|
— |
page_block_name |
str | None
|
— |
layout_chain |
LayoutChain | None
|
— |
context_providers |
tuple[ContextProvider, ...]
|
— |
context |
dict[str, Any]
|
— |
Methods
name
0
str
▼
Deprecated alias for ``template_name``.
property
name
0
str
▼
def name(self) -> str
Returns
str
effective_page_block_name
0
str
▼
Block used when layouts or boosted swaps need the page root.
property
effective_page_block_name
0
str
▼
def effective_page_block_name(self) -> str
Returns
str
Internal Methods 1 ▼
__init__
6
▼
__init__
6
▼
def __init__(self, template_name: str, block_name: str, /, *, page_block_name: str | None = None, layout_chain: LayoutChain | None = None, context_providers: tuple[ContextProvider, ...] = (), **context: Any) -> None
Parameters
| Name | Type | Description |
|---|---|---|
template_name |
— |
|
block_name |
— |
|
page_block_name |
— |
Default:None
|
layout_chain |
— |
Default:None
|
context_providers |
— |
Default:()
|
**context |
— |
OOB
3
▼
Compose a primary response with out-of-band fragment swaps.
htmx processes the first element as th…
OOB
3
▼
Compose a primary response with out-of-band fragment swaps.
htmx processes the first element as the normal swap target, then
scans for elements withhx-swap-ooband swaps them into the
page by ID.OOBrenders all fragments into a single HTML
response with the correct attributes.
Each OOB fragment's target ID defaults to itsblock_name
(convention), but can be overridden viaFragment(..., target="id").
Usage::
return OOB(
Fragment("products.html", "list", products=products),
Fragment("cart.html", "counter", count=new_count),
Fragment("notifications.html", "badge", unread=3),
)
The first fragment is the primary swap target. All subsequent
fragments are rendered withhx-swap-oob="true" and an id
matching their target.
Attributes
| Name | Type | Description |
|---|---|---|
main |
Fragment | Template | Page | LayoutPage | PageComposition
|
— |
oob_fragments |
tuple[Fragment, ...]
|
— |
Methods
Internal Methods 1 ▼
__init__
2
▼
__init__
2
▼
def __init__(self, main: Fragment | Template | Page | LayoutPage | PageComposition, /, *oob_fragments: Fragment) -> None
Parameters
| Name | Type | Description |
|---|---|---|
main |
— |
|
*oob_fragments |
— |
Functions
_validate_swap
1
None
▼
Validate htmx swap strategy, allowing modifiers after base value.
_validate_swap
1
None
▼
def _validate_swap(value: str | None) -> None
Parameters
| Name | Type | Description |
|---|---|---|
value |
str | None |