No-Build High-State

Use Chirp islands and state primitives without bundlers

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

Overview

Chirp lets you build stateful client widgets without React, Vite, or an npm build step. Server-rendered HTML and htmx fragments handle the data; small ES-module islands — self-mounting widgets in/static/islands/*.js— handle client-only state such as drag reorder, multi-step wizards, and optimistic toggles.

Reach for this when a widget needs richer client behavior thanhx-*attributes give you, but you do not want a front-end framework or its build pipeline. For something lighter than an island, Alpine.js covers small interactivity inline in the template.

  • Server rendering: Chirp + Kida templates
  • Partial updates: htmx
  • Stateful widgets: islands with/static/islands/*.jsES modules

Reach for a primitive first

Chirp ships named primitives for the client-state shapes you hit most. Use one of these before reaching for a full framework island:

state_sync, action_queue, draft_store, error_boundary, grid_state, wizard_state, upload_state, optimistic_apply.

You mount a primitive withprimitive_attrs(...), which emits the data-island-primitivemetadata the runtime reads:

<section{{ primitive_attrs("wizard_state", props={"stateKey": "signup", "steps": ["a", "b", "c"]}) }}>
  ...
</section>

Optimistic UI without server state

optimistic_applyis the one primitive whose client runtime Chirp ships, so you mount it without writing any JavaScript. It paints a mutation instantly from the client's own snapshot, lets htmx do the real request, swaps the authoritative server fragment on success, and reverts on failure. The server keeps zero per-client view state: the handler is identical with or without the adapter.

<button hx-post="/toggle-like" hx-target="#like-btn" hx-swap="outerHTML"
        {{ optimistic_attrs([{"op": "toggleClass", "value": "liked"},
                             {"op": "setText", "expr": "+1", "sel": ".count"},
                             {"op": "disable"}], mount_id="like-btn") }}>...</button>

It closes ~80% of the optimistic-UI gap (one in-flight mutation per region, last-write-wins, replacing swaps only). For concurrent collaborative editing, reach for a framework island. Full op contract and guardrail: Islands.

Decision rule

Choose a primitive when:

  • state is local to one widget
  • htmx still handles the server data boundaries
  • you do not need a full client router or runtime

Choose a framework island when:

  • third-party JS libraries force framework lifecycle APIs
  • component complexity becomes a mini-app with deep client-only state

For server-driven realtime (push from the server after the page loads), use signals and SSE instead — see the reactive system.