Interactivity with htmx

Koyo stays static-first and stays on the server for behavior. htmx 2.0 is vendored in, and pages can answer with small HTML fragments that the browser swaps in place.

htmx is built in#

The scaffold ships htmx 2.0.4 at /htmx.min.js and loads it from the root layout:

# app/layout.py
script(src="/htmx.min.js")

Because hx_ attributes pass through the tag factory to their hyphenated form, you write htmx directly in components:

button(
    class_="btn",
    hx_get="/hello",
    hx_swap="outerHTML",
)("Say hello")

Fragments#

A page module can export a fragment() function. When the request carries the HX-Request header (every htmx request does), a fragment route returns just the fragment, without the surrounding layout:

# app/greet/page.py
from koyoapp.html import p

def page():
    # A non-htmx visitor sees the full page.
    return "Full page markup for /greet."

def fragment(request):
    # An htmx request gets only this snippet to swap in.
    return p("Hello from a fragment.")

Koyo adds dedicated support for this pattern: a route file that only defines fragment() always returns a bare fragment, so an hx_post to that route never ships the full page markup down the wire.

Working example: the session counter#

The scaffold's single home page demonstrates the full loop with the counter in components/counter.py:

  1. A button posts to the /__koyo_state URL that use_state's action fills in automatically, see session state.
  2. Koyo increments the session value and re-renders the card with the new count.
  3. hx_target="#visit-card" with hx_swap="outerHTML" swaps the response in place, replacing the old card. No page reload.

Open the home page of your app, click the plus button, and watch the count update without a navigation.

When to use it#

  • Use fragments for counters, forms, search results, tabs, and inline updates.
  • Prefer leaves-provided behavior over hand-written JavaScript; a fragment is still server-rendered HTML.
  • Reserve a full page and heavy client logic for interactions that htmx cannot express well.