hx-indicator: Declarative Loading States via .htmx-request CSS Classes
- Last updated
- Prerequisites:
- CSS Opacity & Display transitions
- HTMX AJAX lifecycle
- htmx
- hx-indicator
- loading-states
- spinner
- css
- htmx-request
Read at your depth
The practical view
`hx-indicator` points to an element that will be shown while an HTMX request is in flight: `<button hx-get="/data" hx-indicator="#spinner">Load</button>`. HTMX automatically adds the `.htmx-request` CSS class to the indicator element (and the triggering element) during the request, and removes it upon completion. The default HTMX stylesheet automatically hides `.htmx-indicator` (`opacity: 0`) and shows it when `.htmx-request` is added (`opacity: 1`).
The same idea in other frameworks
`const [loading, setLoading] = useState(false); return <button disabled={loading}>...`.`<button [disabled]="isLoading"> <span *ngIf="isLoading">...</span>`.`<button :disabled="loading"><Spinner v-if="loading" />`.`<button hx-get="/api" hx-indicator="#spinner">`.Legacy vs modern
Manual Loading State Boilerplate vs Declarative hx-indicator
Manual spinner toggling requires try/finally blocks in JS; `hx-indicator` coordinates loading visibility through CSS classes.
spinner.style.display = 'block'; try { await fetch(); } finally { spinner.style.display = 'none'; } // ❌ Manual<button hx-post="/save" hx-indicator="#spinner">Save</button> // ✅ NEW: Zero JS neededInterview gotchas
Context
Interviewers test your knowledge of default indicator scoping.
The mechanical answer
If no `hx-indicator` attribute is explicitly provided on the triggering element, HTMX looks for any child element matching `.htmx-indicator` *inside* the triggering element and adds `.htmx-request` to the triggering element itself. If the indicator is located outside the element, `hx-indicator="#my-spinner"` must be explicitly specified.
Trap
Placing `.htmx-indicator` on a sibling element without specifying `hx-indicator` will result in the spinner never appearing.
Context
Checks precision regarding relative selector resolution in indicator attributes.
The mechanical answer
Yes. `hx-indicator` supports the same relative selectors as `hx-target`: `hx-indicator="closest tr"`, `hx-indicator="find .spinner"`, `hx-indicator="next .loading-bar"`, or `hx-indicator="this"`.
Trap
Using `hx-indicator="closest .row"` adds the `.htmx-request` class to the entire row, allowing you to dim the whole table row with CSS during updates.