Request action
request-action is the simplest htmx pattern with first-class loading
feedback. The button stays a normal <button>; htmx owns the request;
a sibling .hc-spinner (marked as an htmx-indicator) shows progress.
Live demo
Section titled “Live demo”Click Add item — the button POSTs a real htmx request, the button
disables itself and the spinner shows while in flight, the whole region
is swapped outerHTML, and an HX-Trigger header raises a success
toast. The endpoint is a namespaced demo implementation of the server
contract under api/recipes/request-action/ (a real app would use its
own URL such as /items).
Basic usage
Section titled “Basic usage”<span class="hc-action"> <button class="hc-button" data-variant="primary" type="button" data-hx-post="/items" data-hx-target="#items" data-hx-swap="outerHTML" data-hx-disabled-elt="this" data-hx-indicator="closest .hc-action"> Save </button>
<span class="hc-spinner htmx-indicator" aria-hidden="true"></span></span>What happens:
- The user clicks Save.
- htmx adds
.htmx-requestto the<button>and (viadata-hx-indicator) to the surrounding.hc-actionwrapper. data-hx-disabled-elt="this"disables the button for the duration of the request — htmx adds and removesdisabledautomatically.- The wrapper switches to
cursor: progressand the spinner fades in (hc.htmx.cssstyles.htmx-indicator). - The server returns HTML for the target area; htmx swaps it in.
Pieces
Section titled “Pieces”.hc-action— an inline-flex wrapper that colocates the control and its indicator. While the wrapper contains an.htmx-requestdescendant, its cursor switches toprogress..hc-spinner— a small CSS-only spinner sized by--hc-spinner-size. It inheritscurrentColor, so the indicator color matches the surrounding text..htmx-indicator— htmx convention. Indicators are hidden by default and shown while.htmx-requestis present on the indicator itself or an ancestor.data-hx-disabled-elt="this"— htmx attribute that adds thedisabledattribute to the button for the duration of the request. This prevents double-submits without any custom JavaScript.
Server response contract
Section titled “Server response contract”| Request | Response |
|---|---|
POST /items (success) | 200 + HTML for the data-hx-target — this recipe swaps outerHTML, so the fragment replaces the target itself |
POST /items (success + toast) | 200 + fragment and/or HX-Trigger: {"hc:toast":{"message":"Saved","variant":"success"}} (see the toast recipe) |
POST /items (failure) | 4xx/5xx + body HTML — use HX-Reswap / HX-Retarget to redirect the swap to a flash region if needed |
htmx ≥ 2 does not swap non-2xx responses by default: without
HX-Reswap / HX-Retarget (or an htmx:beforeSwap allowance), the
target keeps its previous content on failure.
Accessibility
Section titled “Accessibility”- The spinner is purely decorative —
aria-hidden="true"keeps it out of the accessibility tree. The button text remains the announceable name. - For long-running requests, consider pairing this recipe with a
status region (
aria-live="polite") elsewhere on the page so screen reader users know when the action completes. data-hx-disabled-elt="this"adds the nativedisabledattribute, which removes the button from the tab order while the request is in flight. This is appropriate for short-lived requests; for longer ones, preferaria-disabled="true"so users can still focus the control.
Progressive enhancement
Section titled “Progressive enhancement”Without htmx, the button does nothing. To keep the action working without JavaScript, wrap the button in a real form:
<form method="post" action="/items"> <button class="hc-button" data-variant="primary" type="submit"> Save </button></form>The htmx attributes can sit on the form itself instead of the button, so the same markup works whether htmx is loaded or not.
Related
Section titled “Related”- Button — the control.
- Spinner — the indicator (small, CSS-only).
- Confirm action — add a confirmation step in front of this recipe.