N

Reactivity patterns

HTMX covers every DX pattern React developers reach for, with a tiny fraction of the toolchain. Each pattern below has a one-page walkthrough with the markup and the server-side handler shape. A runnable demo of each lives in the starter at /patterns/.

If you're coming from TanStack Query, the mental swap is: your server action already is the cache invalidation. Return the fresh partial, htmx swaps it in place, done. No client cache to mutate.

#The 15 patterns

  1. Debounced live search — hx-trigger="input changed delay:400ms".
  2. Server-side sort — sortable column headers, hx-get with hx-push-url.
  3. Paginated list — prev/next + numbered pages, one swap per click.
  4. Infinite scroll — hx-trigger="intersect once" with cursor pagination.
  5. Inline validation (422 target) — return 422 and htmx re-renders the form partial.
  6. Modal form with validation — dialog + form scoped errors, close on success via HX-Trigger.
  7. Optimistic delete — hx-swap="outerHTML swap:0s" removes the row immediately.
  8. Toast after action (out-of-band swap) — server appends
    to refresh toasts everywhere.
  9. Counter sync — badges and totals stay fresh via OOB swaps.
  10. Multi-target update — one request refreshes two regions via hx-swap-oob.
  11. Tabs with lazy content — hx-trigger="click once" loads each tab's body on first reveal.
  12. Async autocomplete (combobox) — hx-trigger="keyup changed delay:300ms".
  13. Polling for progress — hx-trigger="every 2s" until done.
  14. Cascading selects — select-on-change re-renders the next select.
  15. Real-time "is typing" (SSE companion) — hx-ext="sse" for long-lived signal channels.

#Runtime requirements

All patterns work with the starter's default stack:

html
<!-- HTMX (server roundtrips) -->
<script defer src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"></script>
<script defer src="https://unpkg.com/htmx-ext-alpine-morph@2.0.0/alpine-morph.js"></script>

<!-- Alpine (client state) -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/morph@3.14.1/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.14.1/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js"></script>

The PageOrPartial helper in Infrastructure/Web/AjaxExtensions.cs returns a full page for normal GETs and a partial for requests carrying HX-Request: true — so every pattern's handler is one method that works both ways.

#Two layers, clean separation

ConcernToolExample
Server roundtrips, swaps, partialsHTMXhx-get, hx-post, hx-target, hx-swap, hx-trigger, hx-swap-oob
Client-only stateAlpinex-data, x-show, x-on:click, x-cloak, x-persist
Component markupNoundry.UI tags, ,
StyleTailwind v4 via Tailbreezeutility classes, no Node toolchain

The htmx-ext-alpine-morph extension preserves Alpine state across HTMX swaps so checkboxes, expanded panels, and modal-open flags stay correct after a partial re-render.

#When to reach for what

  • Pure server data → HTMX. Forms, lists, sorts, paginations, validation. Server stays the source of truth.
  • Pure client state → Alpine. Modal open/close, dropdown toggles, theme persistence, "show more" expansions.
  • Mixed → both, in that order. Wrap the HTMX trigger in
    if you need a local "is submitting" flag during the round-trip.