htmx Tutorial · User Feedback · Lesson 11 of 30

Loading Indicators

Show a spinner while a request runs with hx-indicator and the htmx-request class, and disable buttons with hx-disabled-elt.

Create a free account to save this page, take notes and track your progress. Sign in

Why indicators matter

Real requests take time. Without feedback, users click again (sending duplicate requests) or think the page is broken. htmx makes loading states almost free.

The htmx-request class

While a request is running, htmx adds the class htmx-request to the element that made it. Any element with the class htmx-indicator inside it becomes visible during the request (htmx includes the CSS: indicators have opacity: 0 normally and opacity: 1 during requests).

hx-indicator="#spinner" points to an indicator somewhere else on the page.

A spinner inside the buttonTry it yourself
<style>.spinner { display: inline-block; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }</style>

<button hx-get="/report" hx-target="#report">
  Load report <span class="htmx-indicator spinner">⏳</span>
</button>
<div id="report"></div>

<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
  server.get("/report", () => "<p>Sales are up 12% this month.</p>", { delay: 1500 });
</script>

Disabling elements during a request

hx-disabled-elt adds the disabled attribute to elements while the request runs, which stops double submissions:

  • hx-disabled-elt="this": the element itself.
  • hx-disabled-elt="find button": the button inside a form.
  • Any CSS selector, or several separated by commas.
An indicator elsewhere and a disabled buttonTry it yourself
<form hx-post="/pay" hx-target="#result" hx-indicator="#saving" hx-disabled-elt="find button">
  <input name="amount" value="25">
  <button>Pay now</button>
  <span id="saving" class="htmx-indicator">Processing payment...</span>
</form>
<p id="result"></p>

<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
  server.post("/pay", (req) => `Paid $${server.escape(req.body.amount)}. (Try clicking twice: the button is disabled.)`, { delay: 2000 });
</script>

Preventing overlapping requests

hx-sync decides what happens when a new request starts while another one from the same place is still running: hx-sync="this:replace" cancels the old one (good for search boxes), this:drop ignores the new one (the default).

Test yourself

Which class does htmx add to the requesting element while its request is in flight?

  1. htmx-request
  2. htmx-loading
  3. htmx-indicator
  4. is-busy
Show answer

Answer: htmx-request

Elements with htmx-indicator inside it (or named by hx-indicator) are shown while htmx-request is present.

Was this page helpful?