Skip to main content

A love letter to CSS and HTML over the wire — gorgeous Chirp components, htmx-native, Alpine.js for interactivity

Project description

chirp-ui

PyPI version Tests Python 3.14+ License: MIT Status: Alpha

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

What is chirp-ui?

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-scheme dark mode.

Installation

# pip
pip install chirp-ui

# uv
uv add chirp-ui

Requires 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.3.0 >= 3.14 >= 0.2.0 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.


Quick Start

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="/static/chirpui.css">
5 For interactive components (dropdown, modal, tray, tabs, theme toggle): call use_chirp_ui(app) — Chirp auto-injects Alpine.js with all required plugins

Serve assets:

from chirp.middleware.static import StaticFiles
import chirp_ui

app.add_middleware(StaticFiles(
    directory=str(chirp_ui.static_path()),
    prefix="/static"
))

Features

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

Usage

Component Showcase — Browse all components
pip install chirp chirp-ui
python examples/component-showcase/app.py

Open http://localhost:8000

Theming — 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="/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:mount
  • chirp:island:unmount
  • chirp:island:remount
  • chirp:island:state
  • chirp:island:action
  • chirp: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_sync
  • action_queue
  • draft_store
  • error_boundary
  • grid_state
  • wizard_state
  • upload_state
SSE and streaming — htmx + Server-Sent Events
  • streaming_block — Use sse_swap_target=true for htmx SSE fragment swaps.
  • model_card — Use sse_connect=url, sse_streaming=true for LLM comparison UIs.
  • copy_btn — Copy button with data-copy-text. Enable AppConfig(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(...) and fragment_island_with_result(...) for isolated refresh regions
  • confirm_dialog(...) and confirm_trigger(...) for confirmation flows
  • poll_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.


Key Ideas

  • 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-ui is 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 respect prefers-reduced-motion.
  • Composable. {% slot %} for content injection. Components nest freely. No wrapper classes.
  • Minimal dependency. kida-templates only. Chirp optional for auto-registration.

Requirements

  • Python >= 3.14
  • kida-templates >= 0.3.0

Interactive components (dropdown, modal, tray, tabs, theme toggle, copy button) require Alpine.js 3.x. When using Chirp, call use_chirp_ui(app) — this auto-enables Alpine injection with all required plugins (Mask, Intersect, Focus) and the Alpine.safeData() helper for htmx-safe component registration. For standalone setups without Chirp, include Alpine manually.


Development

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
Docs site + showcase uv sync --group docs then uv run poe docs-build-all (writes site/public/, not tracked in git; GitHub Pages builds in CI)

The Bengal Ecosystem

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 Docs
=^..^= Pounce ASGI server Docs
)彡 Kida Template engine Docs
ฅᨐฅ Patitas Markdown parser Docs
⌾⌾⌾ Rosettes Syntax highlighter Docs

Python-native. Free-threading ready. No npm required.


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chirp_ui-0.2.5.tar.gz (208.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chirp_ui-0.2.5-py3-none-any.whl (236.6 kB view details)

Uploaded Python 3

File details

Details for the file chirp_ui-0.2.5.tar.gz.

File metadata

  • Download URL: chirp_ui-0.2.5.tar.gz
  • Upload date:
  • Size: 208.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for chirp_ui-0.2.5.tar.gz
Algorithm Hash digest
SHA256 d3996fea0153375a710f20e7cc9f702fea888e3af01d3e98b85623c2a4532f4a
MD5 01d62c4f65118fe07f3d41d9d4d733f5
BLAKE2b-256 2d8195509870448e21b68e1e1d7a9507be3df690a6eff19b199d5c2838a3a4c8

See more details on using hashes here.

Provenance

The following attestation bundles were made for chirp_ui-0.2.5.tar.gz:

Publisher: python-publish.yml on lbliii/chirp-ui

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chirp_ui-0.2.5-py3-none-any.whl.

File metadata

  • Download URL: chirp_ui-0.2.5-py3-none-any.whl
  • Upload date:
  • Size: 236.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for chirp_ui-0.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 029a3b35fba01c937073483cc46e76fc2453cffd45887b81fdabbab5429377ee
MD5 8f58ba692b8d79a0854a2d778d1788f0
BLAKE2b-256 32994d3f0814403dee45098143bdc64e53a9eb82e5e81696eedb1e8c32d033a3

See more details on using hashes here.

Provenance

The following attestation bundles were made for chirp_ui-0.2.5-py3-none-any.whl:

Publisher: python-publish.yml on lbliii/chirp-ui

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page