An optional, opinionated UI layer for Chirp — gorgeous by default, htmx-native.
pip install chirp chirp-ui{% from "chirpui/layout.html" import container, grid, block %}
{% from "chirpui/card.html" import card %}
{% call container() %}
{% call grid(cols=2) %}
{% call block() %}{% call card(title="Hello") %}<p>Card one.</p>{% end %}{% end %}
{% call block() %}{% call card(title="World") %}<p>Card two.</p>{% end %}{% end %}
{% end %}
{% end %}chirp-ui is an optional companion design system for Chirp. It provides Kida template macros — cards, modals, forms, layouts — that render as HTML. It is one polished, opinionated way to build Chirp apps, not the framework itself and not the only way to use Chirp. Use it with htmx for swaps, SSE for streaming, and View Transitions for polish. No client-side framework required for layout.
What's good about it:
- Gorgeous by default — Full visual design out of the box. Override
--chirpui-*CSS variables to customize. - htmx-native — Interactive components use htmx or Alpine.js. Dropdown, modal, tray, tabs, theme toggle, copy button use Alpine for declarative behavior.
- Composable —
{% slot %}for content injection. Components nest freely. - Modern CSS —
:has(), container queries, fluid typography,prefers-color-schemedark mode.
# pip
pip install chirp-ui
# uv
uv add chirp-uiRequires Python 3.14+. When used with Chirp, components are auto-detected — no configuration needed.
You do not need chirp-ui to use Chirp. Use it when you want the companion component library, default design language, and app-shell patterns.
Version compatibility:
| chirp-ui | Kida | Python | Chirp (optional) |
|---|---|---|---|
| 0.2.x | >= 0.2.8 | >= 3.14 | >= 0.1.6 for use_chirp_ui |
What's stable: Core macros (layout, card, modal, forms, alert, badge), filters (bem, html_attrs, validate_variant, validate_size), VARIANT_REGISTRY/SIZE_REGISTRY, static path, get_loader, register_filters. See SECURITY.md for | safe usage.
What may change: New component variants/sizes, API surface of __all__-excluded internals, Alpine.js migration patterns.
| Step | Action |
|---|---|
| 1 | Install Chirp and chirp-ui if you want the companion UI layer: pip install chirp chirp-ui |
| 2 | Serve static assets from the package (CSS, themes) |
| 3 | Import macros in templates: {% from "chirpui/card.html" import card %} |
| 4 | Include CSS: <link rel="stylesheet" href="proxy.php?url=/static/chirpui.css"> |
| 5 | For interactive components (dropdown, modal, tray, tabs, theme toggle): use chirpui/app_shell_layout.html or chirpui/app_layout.html — both include Alpine.js |
Serve assets:
from chirp.middleware.static import StaticFiles
import chirp_ui
app.add_middleware(StaticFiles(
directory=str(chirp_ui.static_path()),
prefix="/static"
))| Feature | Description |
|---|---|
| Layout | container, grid, stack, block, page_header, section_header, divider, breadcrumbs, navbar, navbar_end, navbar_dropdown, sidebar, hero, surface, callout |
| UI | card, card_header, modal, drawer, tabs, accordion, dropdown, popover, toast, table, pagination, alert, button_group, island_root, state primitives |
| Forms | text_field, password_field, textarea_field, select_field, checkbox_field, toggle_field, radio_field, file_field, date_field, csrf_hidden, form_actions, login_form, signup_form |
| Data display | badge, spinner, skeleton, progress, description_list, timeline, tree_view, calendar |
| Dashboard | inline_edit_field, row_actions, status_with_hint, entity_header, confirm_dialog, confirm_trigger, fragment_island, poll_trigger — DASHBOARD-MATURITY-CONTRACT |
| Docs | page_hero, nav_tree, params_table, signature, index_card — framework-agnostic docs components |
| Streaming | streaming_block, copy_btn, model_card — for htmx SSE and LLM UIs |
| Theming | --chirpui-* CSS variables, dark mode, optional Holy Light theme |
| Component options | COMPONENT-OPTIONS.md — valid variants, sizes, strict mode |
Component Showcase — Browse all components
pip install chirp chirp-ui
python examples/component-showcase/app.pyTheming — Override CSS variables
chirp-ui uses prefers-color-scheme for dark mode. Override any --chirpui-* variable. Base colors drive derived states (hover, active, light, muted) via color-mix():
:root {
--chirpui-accent: #7c3aed;
--chirpui-container-max: 80rem;
/* Optional: tune shade ratios for all colors */
--chirpui-shade-hover: 85%;
--chirpui-shade-muted: 15%;
}Advanced tokens: HTTP methods (--chirpui-method-get, etc.), code syntax (--chirpui-code-keyword, etc.), secondary accent (--chirpui-accent-secondary, --chirpui-alert-secondary-*). Full token reference: PLAN-theme-tokens.md.
For manual light/dark toggle, set data-theme="light" or data-theme="dark" on <html>.
Optional theme: <link rel="stylesheet" href="proxy.php?url=/static/themes/holy-light.css">
Manual registration — Use with Kida without Chirp
from kida import ChoiceLoader, Environment, FileSystemLoader
from chirp_ui import get_loader
env = Environment(
loader=ChoiceLoader([
FileSystemLoader("templates"),
get_loader(),
])
)Call chirp_ui.register_filters(app) if using Chirp for form/field helpers.
Islands (framework-agnostic) — Isolate high-state widgets
chirp-ui stays server-rendered by default. For complex client-state widgets (editors, canvases, advanced grids), mount isolated islands on dedicated roots.
{% from "chirpui/islands.html" import island_root %}
{% call island_root("editor", props={"doc_id": doc.id}, mount_id="editor-root") %}
<p>Fallback editor UI (SSR) if JavaScript is unavailable.</p>
{% end %}In Chirp, enable runtime lifecycle hooks:
from chirp import App, AppConfig
app = App(AppConfig(islands=True, islands_contract_strict=True))Lifecycle events emitted in the browser:
chirp:island:mountchirp:island:unmountchirp:island:remountchirp:island:statechirp:island:actionchirp:island:error
For no-build defaults, use primitive wrappers from chirpui/state_primitives.html:
{% from "chirpui/state_primitives.html" import grid_state, wizard_state, upload_state %}
{% call grid_state("team_grid", ["name", "role"], mount_id="grid-root") %}
...
{% end %}Included no-build primitives:
state_syncaction_queuedraft_storeerror_boundarygrid_statewizard_stateupload_state
SSE and streaming — htmx + Server-Sent Events
- streaming_block — Use
sse_swap_target=truefor htmx SSE fragment swaps. - model_card — Use
sse_connect=url,sse_streaming=truefor LLM comparison UIs. - copy_btn — Copy button with
data-copy-text. EnableAppConfig(delegation=True)for dynamically inserted buttons.
See Chirp RAG demo and LLM playground.
Dashboard refresh patterns — fragment islands, confirm flows, polling
For server-driven admin and settings pages, chirp-ui includes dashboard helpers that remove a lot of repeated htmx markup:
fragment_island(...)andfragment_island_with_result(...)for isolated refresh regionsconfirm_dialog(...)andconfirm_trigger(...)for confirmation flowspoll_trigger(url, target, delay=...)for hidden load/delay polling
{% from "chirpui/fragment_island.html" import poll_trigger %}
<div id="collections-results"></div>
{{ poll_trigger("/collections/status?refresh=1", "#collections-results", delay="1s") }}These are especially useful in app-shell layouts where one panel refreshes in place while the rest of the shell stays stable.
Alpine Magics & Events
chirp-ui uses Alpine.js magics for accessibility and cross-component communication:
| Magic | Use |
|---|---|
$el |
Current element (e.g. $el.dataset.label, $el.value) |
$refs |
DOM refs for focus management (dropdown trigger/panel) |
$store |
Global state (modals, trays) for overlay components |
$id |
Unique IDs for ARIA (dropdown, tabs) |
$watch |
Reactive sync (theme/style select) |
$dispatch |
Custom events for app-level handling |
$nextTick |
Post-render focus (dropdown_select) |
Custom events — Listen for chirpui:* events on document or a parent:
| Event | When | Detail |
|---|---|---|
chirpui:dropdown-selected |
Dropdown item clicked | { label, href? } or { label, action? } or { label, value? } |
chirpui:tab-changed |
Tab clicked | { tab } |
chirpui:tray-closed |
Tray backdrop/close clicked | { id } |
chirpui:modal-closed |
Modal backdrop/close clicked | { id } |
Example: run HTMX or analytics when a dropdown item is selected:
document.addEventListener('chirpui:dropdown-selected', (e) => {
if (e.detail.action) htmx.ajax('POST', '/api/action', { values: { action: e.detail.action } });
});Full reference: docs/ALPINE-MAGICS.md
Icons and ergonomics
Many components support Unicode icons via the icon param:
{% from "chirpui/alert.html" import alert %}
{% from "chirpui/card.html" import card %}
{% from "chirpui/button.html" import btn %}
{% from "chirpui/callout.html" import callout %}
{% call alert(variant="warning", icon="⚠", title="Heads up") %}Body text{% end %}
{% call card(title="Feature", subtitle="Optional subtitle", icon="◆") %}Content{% end %}
{{ btn("Save", icon="✓") }}
{% call callout(icon="💡", title="Tip") %}Use Unicode for icons.{% end %}For animated icons, use ascii_icon() in the component slot. For custom headers with actions, use the header_actions named slot (Kida 0.3+):
{% from "chirpui/card.html" import card %}
{% call card(title="Settings", icon="⚙") %}
{% slot header_actions %}
<button class="chirpui-btn chirpui-btn--ghost">⋯</button>
{% end %}
<p>Body content.</p>
{% end %}empty_state supports action_label and action_href for a primary CTA button.
- HTML over the wire. Components render as blocks for htmx swaps, SSE streams, and View Transitions. The server is the source of truth.
- Companion, not core.
chirp-uiis an optional layer on top of Chirp, not a requirement for using the framework. - CSS as the design language. Modern features (
:has(),aspect-ratio,clamp()) used where they add value. All animations respectprefers-reduced-motion. - Composable.
{% slot %}for content injection. Components nest freely. No wrapper classes. - Minimal dependency.
kida-templatesonly. Chirp optional for auto-registration.
- Python >= 3.14
- kida-templates >= 0.2.6
Interactive components (dropdown, modal, tray, tabs, theme toggle, copy button) require Alpine.js 3.x. The chirpui/app_shell_layout.html and chirpui/app_layout.html layouts include Alpine via CDN. For custom layouts, add Alpine before using these components.
git clone https://github.com/lbliii/chirp-ui.git
cd chirp-ui
uv sync --group dev
pytest| Task | Command |
|---|---|
| Run tests | uv run pytest or poe test |
| Type check | uv run ty check src/chirp_ui/ or poe ty |
| Lint | uv run ruff check . or poe lint |
| Full CI | poe ci |
A structured reactive stack written in pure Python for 3.14t free-threading. Chirp is the framework; chirp-ui is one optional UI layer built on top of it.
| ᓚᘏᗢ | Bengal | Static site generator | Docs |
| ∿∿ | Purr | Content runtime | — |
| ⌁⌁ | Chirp | Web framework | Docs |
| ʘ | chirp-ui | Optional companion UI layer ← You are here | — |
| =^..^= | Pounce | ASGI server | Docs |
| )彡 | Kida | Template engine | Docs |
| ฅᨐฅ | Patitas | Markdown parser | Docs |
| ⌾⌾⌾ | Rosettes | Syntax highlighter | Docs |
Python-native. Free-threading ready. No npm required.
MIT