Module

templating.returns

Template, Fragment, Page, Stream, TemplateStream, and ValidationError return types.

Frozen dataclasses that handlers return. The content negotiation layer inspects these to dispatch to the kida renderer.

Classes

Template 5
Render a full kida template. Usage:: return Template("page.html", title="Home", items=items)

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
def name(self) -> str
Returns
str
inline 2 InlineTemplate
Create a template from a string. For prototyping only. Usage:: return Te…
staticmethod
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
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…

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
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…

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 isNone(the default), the block name is used as the target ID.
  • SSE streams: target becomes the SSE event name. Templates usesse-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
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.…

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-Request without HX-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
def name(self) -> str
Returns
str
effective_page_block_name 0 str
Block used when a full page fragment root is required.
property
def effective_page_block_name(self) -> str
Returns
str
mounted 4 Page
Create a Page for mounted page-directory/app-shell templates.
staticmethod
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
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`…

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…

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-Redirect to redirectURL (client-side full redirect).
  • non-htmx: 303 redirect toredirectURL.

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
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…

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
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 …

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
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…

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
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…

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
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…

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
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…

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
def name(self) -> str
Returns
str
effective_page_block_name 0 str
Block used when layouts or boosted swaps need the page root.
property
def effective_page_block_name(self) -> str
Returns
str
Internal Methods 1
__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…

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
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.
def _validate_swap(value: str | None) -> None
Parameters
Name Type Description
value str | None