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's canonical block ending is {% end %}. Matching explicit closers such as
{% endif %}, {% endfor %}, and {% endblock %} are also accepted, so an
otherwise compatible Jinja template does not need closer-only edits.
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. See the App-Owned Component Authoring Contract for extraction heuristics, composition seams, and framework/application ownership boundaries.
| 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 parsed and compiled directly without entering template or
bytecode caches, so compiler warnings and analysis findings share one report.
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.
Explicit framework/app roots use stable namespaces in both the CLI and public inspection API:
kida check --root framework=framework/templates --root app=app/templates --validate-calls
kida components --root framework=framework/templates --root app=app/templates --jsonPython adapters can pass a configured Environment to
kida.inspection.diagnose_roots() and inspect_components() so their filters,
globals, tests, and extensions participate without ambient discovery or a new
configuration-file surface.
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.
Moving from 0.11.x? See the 0.12 release notes for programmatic diagnostics, literal block metadata, and expanded free-threading proof.
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.