Server-side components for Python — no npm, no build step.
Kida brings typed props, named slots, static call-site validation, scoped state, and error boundaries to pure-Python templates. Components render to HTML, Markdown, terminal output, and CI reports on free-threaded Python 3.14t, with no runtime dependencies.
pip install kida-templates{% def card(title: str, variant: str = "default") %}
<article class="card card--{{ variant }}">
<h3>{{ title }}</h3>
{% if has_slot("header_actions") %}
<div class="actions">{% slot header_actions %}</div>
{% endif %}
<div class="body">{% slot %}</div>
</article>
{% enddef %}
{% call card("Settings", variant="elevated") %}
{% slot header_actions %}<button>Save</button>{% end %}
<p>Configure your preferences.</p>
{% endcall %}
from kida import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates/"))
template = env.get_template("page.html")
html = template.render(title="Hello")Kida catches component mistakes before a user sees a page, report, or terminal screen.
{% def badge(count: int, label: str) %}
<span class="badge">{{ count }} {{ label }}</span>
{% enddef %}
{{ badge(count="five", lable="Messages") }}
kida check templates/ --strict --validate-callstemplates/dashboard.html:5: K-CMP-001: Call to 'badge' — unknown params: lable; missing required: label
templates/dashboard.html:5: K-CMP-002: type: badge() param 'count' expects int, got str ('five')
Validation catches unknown params, missing required params, and literal type mismatches at check time.
| Surface | What Kida gives you |
|---|---|
| Web apps | Component templates for Flask, FastAPI, Django, Chirp, and Bengal |
| Static sites | Reusable layouts, slots, typed content components, and scoped state |
| CI reports | Markdown step summaries and PR comments from pytest, coverage, ruff, ty, and more |
| Terminal tools | ANSI-aware tables, badges, panels, dashboards, and progress output |
| Framework tooling | Template metadata, block rendering, component discovery, and dependency analysis |
Kida brings frontend-style composition to ordinary Python templates.
| Feature | Syntax |
|---|---|
| Typed props | {% def card(title: str, count: int = 0) %} |
| Named slots | {% slot header %} / {% slot %} (default) |
| Conditional slots | has_slot("footer") |
| Scoped slots (data up) | {% slot row let:item=item %} |
| Slot forwarding | {% yield name %} |
| Context propagation | {% provide theme = "dark" %} / consume("theme") |
| Error boundaries | {% try %}...{% fallback error %}...{% endtry %} |
| Co-located styles | {% push "styles" %} / {% stack "styles" %} |
| Pattern matching | {% match status %}{% case "active" %}...{% endmatch %} |
| Block-scoped variables | {% set %} (scoped) / {% let %} (template-wide) / {% export %} |
kida components templates/
# components/card.html
# def card(title: str, subtitle: str | None = None)
# slots: header_actions, footer
#
# components/button.html
# def button(label: str, variant: str = "primary")
# slots: (none)
#
# 2 component(s) found.template = env.get_template("components/card.html")
meta = template.def_metadata()
card = meta["card"]
print(card.params) # (DefParamInfo(name='title', annotation='str', ...), ...)
print(card.slots) # ('header_actions', 'footer')
print(card.has_default_slot) # TrueOne template syntax can target HTML, terminal output, Markdown, and CI reports.
HTML
from kida import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates/"))
html = env.get_template("page.html").render(title="Hello")Terminal
from kida.terminal import terminal_env
env = terminal_env()
template = env.from_string("""
{{ "Deploy Status" | bold | cyan }}
{{ hr(40) }}
{% for svc in services %}
{{ svc.name | pad(20) }}{{ svc.status | badge }}
{% endfor %}
""")
print(template.render(services=[
{"name": "api", "status": "pass"},
{"name": "worker", "status": "fail"},
]))Markdown
from kida.markdown import markdown_env
env = markdown_env()
md = env.from_string("# {{ title }}\n\n{{ body }}").render(
title="Report", body="All tests passed."
)CI Reports (GitHub Action)
Turn pytest, coverage, ruff, and other tool output into step summaries and PR comments.
- uses: lbliii/kida@v0
with:
template: pytest
data: results.xml
data-format: junit-xml
post-to: step-summary,pr-commentBuilt-in templates for pytest, coverage, ruff, ty, jest, gotest, sarif, release notes, and AMP agent reports. Full action docs →
Kida does not rely on the GIL for correctness. Templates compile to immutable
Python code, render state lives in ContextVar, and environment mutation uses
copy-on-write patterns. Public APIs are safe under PYTHON_GIL=0 on
free-threaded Python 3.14t.
The no-GIL badge is backed by a required Python 3.14t pull-request lane covering
the full suite, focused thread-safety and async tests, and benchmark regression.
Weekly and manual runs add 25 seeded schedules (10,000 shared-runtime operations)
plus Python development mode, allocator debug hooks, and faulthandler.
The claim covers Kida's documented sharing contract,
not unsynchronized application state, custom callables, external source mutation,
ThreadSanitizer, or a CPython Py_DEBUG build.
| Traditional templates | Kida | |
|---|---|---|
| Typed parameters | Usually no | param: str | None |
| Named slots | Usually no | {% slot name %} |
| Scoped variables | Often leak or surprise | set is block-scoped |
| Context propagation | Prop drilling | provide / consume |
| Error boundaries | Rare | {% try %}...{% fallback %} |
| Component styles | Disconnected CSS files | {% push "styles" %} |
| Call-site validation | Runtime errors | Compile-time checks |
| Component discovery | Read every file | kida components CLI |
| Block rendering | Framework-specific | render_block() for HTMX partials |
| Streaming | Varies | render_stream() and render_stream_async() |
| Free-threading | Not usually designed for it | GIL-free on Python 3.14t |
Template Inheritance
{# base.html #}
<!DOCTYPE html>
<html>
<body>{% block content %}{% endblock %}</body>
</html>
{# page.html #}
{% extends "base.html" %}
{% block content %}<h1>{{ title }}</h1>{% endblock %}
Regions (Parameterized Blocks)
{% region sidebar(current_path="/") %}
<nav>{{ current_path }}</nav>
{% endregion %}
{{ sidebar(current_path="/about") }}
Regions are blocks (for render_block()) and callables (for inline use). Ideal
for HTMX OOB swaps.
Pattern Matching & Null Safety
{% match status %}
{% case "active" %}Active{% case "pending" %}Pending{% case _ %}Unknown
{% endmatch %}
{{ user.nickname ?? user.name ?? "Anonymous" }}
{{ config?.database?.host }}
{{ data ?|> parse ?|> validate ?|> render }}
Streaming & Block Rendering
# Stream chunks as they render
for chunk in template.render_stream(items=large_list):
response.write(chunk)
# Render a single block (HTMX partials)
html = template.render_block("content", title="Hello")
# Compose layouts with pre-rendered blocks
html = layout.render_with_blocks({"content": inner_html}, title="Page")Compile-Time Optimization
template = env.from_string(source, static_context={
"site": site_config, "settings": app_settings,
})
html = template.render(page_title="Home", items=page_items)Pure filters can be evaluated at compile time, dead branches can be removed, and
small components with constant args can be inlined. Use
kida render template.html --explain to see active optimizations.
Framework Integration
# Flask
from kida.contrib.flask import init_kida, render_template
kida_env = init_kida(app)
# Starlette / FastAPI
from kida.contrib.starlette import KidaTemplates
templates = KidaTemplates(directory="templates")
# Django
TEMPLATES = [{"BACKEND": "kida.contrib.django.KidaTemplates", ...}]Programmatic Diagnostics
from kida.diagnostics import DiagnosticOptions, apply_safe_edits, diagnose_source
report = diagnose_source(
unsaved_source,
name="page.html",
environment=env,
options=DiagnosticOptions(validate_calls=True, typed=True, a11y=True),
)
for finding in report.diagnostics:
print(finding.code, finding.message, finding.span)
updated_source = apply_safe_edits(unsaved_source, report.diagnostics, path="page.html")Source buffers are analyzed directly without entering template or bytecode
caches. Safe edits are snapshot-checked and overlap-checked before application.
Use diagnose_directory() for programmatic parity with kida check. A supplied
environment can also run namespaced Extension.diagnose() hooks with immutable
source, AST, and visible component-signature context.
CLI
kida render template.txt --data context.json
kida check templates/ --validate-calls --a11y --typed
kida components templates/ --json
kida fmt templates/
kida extract templates/ -o messages.potKida is pre-1.0 and used standalone, through mainstream Python frameworks, and across the broader Python rendering stack. The API can still move, but the core design goals are stable: pure Python, static validation, render-surface parity, and free-threaded safety.
Moving from 0.6.x? See the Upgrade to 0.7 tutorial
for the strict_undefined=True migration patterns.
Moving from 0.7.x? See the Upgrade to 0.8 tutorial
for the Mapping behavior change in null-safe access (?. and ?[...]).
Moving from 0.8.x? See the 0.9 release notes
for the Markdown escaping and | safe trust-boundary changes.
Moving from 0.9.x? See the 0.10 release notes for structured diagnostics and source-attribution hardening.
Moving from 0.10.x? See the 0.11 release notes for framework quickstarts, release hardening, and internal compiler modularization.
Kida is the component layer in a broader pure-Python stack built for 3.14t free-threading. Bengal remains a supported legacy integration, but no longer defines the stack's product direction.
| ᓚᘏᗢ | Bengal | Legacy static-site integration | Docs |
| ∿∿ | Purr | Content runtime | — |
| ⌁⌁ | Chirp | Web framework | Docs |
| =^..^= | Pounce | ASGI server | Docs |
| )彡 | Kida | Component framework | Docs |
| ฅᨐฅ | Patitas | Markdown parser | Docs |
| ⌾⌾⌾ | Rosettes | Syntax highlighter | Docs |
| ᓃ‿ᓃ | Milo | Terminal UI framework | Docs |
MIT License — see LICENSE for details.