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.
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”-
On success — return HTML for the
data-hx-target. The default swap mode isinnerHTML; this recipe usesouterHTMLso the responding fragment replaces the target itself. -
For a toast — include an
HX-Triggerheader (see the toast recipe when shipped):HX-Trigger: {"hc:toast":{"message":"Saved","variant":"success"}} -
On error — return a 4xx/5xx with body HTML; use
HX-ReswaporHX-Retargetto redirect the swap to a flash region if needed.
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.