Skip to content

lbliii/kida

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

)彡 Kida

PyPI version Build Status Python 3.14+ License: MIT

A Python component framework for HTML — typed props, named slots, scoped state, error boundaries, and zero JavaScript.

Kida compiles templates to Python AST, renders to HTML/terminal/markdown, and scales across cores on free-threaded Python 3.14t. Zero runtime dependencies.

Quick Start

pip install kida-templates
{% def card(title: str, variant: str = "default") %}
<article class="card card--{{ variant }}">
  <h3>{{ title }}</h3>
  <div class="actions">{% slot header_actions %}</div>
  <div class="body">{% slot %}</div>
</article>
{% end %}

{% call card("Settings", variant="elevated") %}
  {% slot header_actions %}<button>Save</button>{% end %}
  <p>Configure your preferences.</p>
{% end %}
from kida import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("templates/"))
template = env.get_template("page.html")
html = template.render(title="Hello")

Component Model

Kida gives you the composition patterns of modern frontend frameworks, without a build step.

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 %}...{% end %}
Co-located styles {% push "styles" %} / {% stack "styles" %}
Pattern matching {% match status %}{% case "active" %}...{% end %}
Block-scoped variables {% set %} (scoped) / {% let %} (template-wide) / {% export %}

Static Validation

kida check templates/ --validate-calls
card.html:14: type: badge() param 'count' expects int, got str ("five")
dashboard.html:8: Call to 'card' — unknown params: titl

Catches unknown params, missing required params, and literal type mismatches at check time.

Component Discovery

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.

Introspection API

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) # True

Why Not Jinja2?

Jinja2 Kida
Typed parameters No param: str | None
Named slots No (caller() only) {% slot name %}
Scoped variables set leaks out of blocks set is block-scoped
Context propagation Prop drilling provide / consume
Error boundaries No {% 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 No render_block() for HTMX partials
Streaming Limited render_stream() for chunked HTTP/SSE
Free-threading No GIL-free on Python 3.14t

Render Surfaces

One template syntax, four outputs.

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 }}
{% end %}
""")
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.3.3
  with:
    template: pytest
    data: results.xml
    data-format: junit-xml
    post-to: step-summary,pr-comment

Built-in templates for pytest, coverage, ruff, ty, jest, gotest, and sarif. Full action docs →


More Features

Template Inheritance
{# base.html #}
<!DOCTYPE html>
<html>
<body>{% block content %}{% end %}</body>
</html>

{# page.html #}
{% extends "base.html" %}
{% block content %}<h1>{{ title }}</h1>{% end %}
Regions (Parameterized Blocks)
{% region sidebar(current_path="/") %}
  <nav>{{ current_path }}</nav>
{% end %}

{{ 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
{% end %}

{{ 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)

67 pure filters evaluated at compile time. Dead branches removed. Component inlining for small defs with constant args. Use kida render template.html --explain to see active optimizations.

Free-Threading

All public APIs are safe under PYTHON_GIL=0 (Python 3.14t, PEP 703). Templates compile to immutable AST, rendering uses thread-local accumulators, and the Environment uses copy-on-write. Scales linearly with cores.

Framework Integration
# Flask
from kida.contrib.flask import KidaFlask
kida = KidaFlask(app)

# Starlette / FastAPI
from kida.contrib.starlette import KidaStarlette
templates = KidaStarlette(directory="templates")

# Django
TEMPLATES = [{"BACKEND": "kida.contrib.django.KidaDjango", ...}]
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.pot

The Bengal Ecosystem

Kida is part of a pure-Python stack built for 3.14t free-threading.

ᓚᘏᗢ Bengal Static site generator 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

License

MIT License — see LICENSE for details.

About

)彡 Kida — Free-threading template engine for Python 3.14+ with AST compilation and modern syntax

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages