Welcome to our next lesson. In our previous sessions, we focused on making our HTMX applications more robust and navigable, first with out-of-band swaps and then by using hx-push-url to manage browser history. These features ensure the application state is coherent and accessible. However, there's a crucial gap in the user experience we haven't yet addressed: what happens during the time between a user action and the server's response?
In a React application, you would typically manage this with an isLoading state variable, conditionally rendering a spinner or disabling a button. This lesson will show you the HTMX equivalent, fulfilling the learning outcome: Display loading indicators during in-flight requests using hx-indicator and the htmx-request class. We'll see how HTMX uses a simple, CSS-driven approach to provide immediate visual feedback, which is essential for managing user expectations and improving perceived performance.
The Landscape of Loading Indicators
Before we dive into the mechanics, it's helpful to recognize the variety of loading indicators used in modern UIs. They range from simple spinners to more sophisticated skeleton screens.

Your goal as a developer is to choose the indicator that best fits the context of the user's action. HTMX provides a flexible mechanism that can support any of these patterns.
The Core Mechanism: The htmx-request Class
The entire system for handling loading states in HTMX revolves around a single, powerful concept: the htmx-request CSS class. Here’s how it works:
- An element triggers an HTMX-powered AJAX request (e.g., via
hx-getorhx-post). - HTMX immediately adds the
htmx-requestclass to that element. - The request proceeds.
- Once the request is complete (the response has been received and swapped), HTMX removes the
htmx-requestclass.
This automatic class toggling is the hook we need. By writing CSS rules that target .htmx-request, we can show, hide, or style elements only when a request is in flight.
While applying the class to the triggering element is useful, we often want the indicator to be a separate element—a spinner next to a button, or an overlay on top of a data table. This is where the hx-indicator attribute comes in. You place this attribute on the triggering element and use a CSS selector to point to the element you want to act as the indicator. HTMX will then apply the htmx-request class to that indicator element instead.
Let's watch a video from Net Ninja that demonstrates this core loop in an Express.js application, which should feel very familiar.
Build an "SPA" with HTMX #7 - Making a Loader with hx-indicator
This video provides a clear, step-by-step walkthrough of creating a basic loading indicator.
Simulating Delay: Watch from the beginning to this point to see how a setTimeout is used in an Express route handler to simulate network latency. This makes the need for a loading indicator obvious. Using hx-indicator: In the segment from here, notice how hx-indicator is added to the <body> tag. By placing it on a parent, any HTMX request triggered by a child element will activate the indicator. The value ".loader" is the CSS selector pointing to the indicator element. Creating the Indicator: The section from this point shows the creation of the simple loader.pug partial and its inclusion in the main layout. The CSS Magic: The most important part is from here. The instructor explains the CSS that makes it all work: the .loader is hidden by default (opacity: 0), and a second rule, .loader.htmx-request, makes it visible (opacity: 1) only when HTMX adds the magic class. Finally, watch the result to see it all come together.
A Deeper Dive into Indicator Patterns
The basic opacity toggle is just the beginning. With your experience in frontend development, you know that different situations call for different feedback patterns. The following resource, an excerpt from the book ASP.NET Core Reimagined with htmx, provides a superb catalogue of professional-grade indicator patterns. Although the backend examples use C#, the HTML, CSS, and HTMX principles are universal and directly applicable.
Visual Feedback with hx-indicator and hx-preserve
This resource is our main guide to implementing various robust loading indicator patterns.
First, read the explanation of how hx-indicator functions. It reinforces what we just saw in the video. Next, study the CSS patterns. Pay attention to the two approaches: one for a standalone indicator and another using a descendant selector (.htmx-request .htmx-indicator) for an indicator nested inside the triggering element. Now, let's look at the most common patterns. Button Spinners: Read the section on Button Loading States. The CSS here is particularly useful, as it shows how to hide the button's text and show a nested loading message/spinner during the request. This is a direct replacement for the logic you'd write to handle a form's isSubmitting state in React. Content Overlays: Review the pattern for Table and List Loading Overlays. When fetching new data for a table or list (e.g., for pagination or filtering), placing a semi-transparent overlay on top of the existing, stale data is excellent UX. The CSS using position: absolute is the key here. Skeleton Loaders: Finally, examine the section on Skeleton Loaders. You are certainly familiar with this pattern from your work with React. It provides a better user experience than a spinner because it sets an expectation for the content's layout. The provided CSS with a shimmering animation is a great starting point for your own implementations.
Accessibility: Don't Leave Users in the Dark
Visual feedback is only half the story. Users of assistive technologies, like screen readers, also need to know that something is happening. A silent delay can be even more disorienting for them.
It is crucial to augment your loading indicators with ARIA (Accessible Rich Internet Applications) attributes to communicate the loading state programmatically.
Visual Feedback with hx-indicator and hx-preserve
We'll revisit the same resource to focus on the critical accessibility aspect.
Please read the section titled "Accessibility". The key takeaways are: aria-busy="true": Add this attribute to a container to signal that its contents are currently updating. You can manage this with a bit of JavaScript listening to HTMX events, or sometimes by including it in the initial skeleton loader state. role="status": This, often combined with a visually hidden text element (e.g., "Loading content..."), announces a message to screen readers. aria-live="polite": When placed on a container, this tells screen readers to announce changes to its content when the user is idle. This is perfect for the container that will receive the new content from HTMX.
Integrating these attributes ensures that the responsive feel you create for visual users is also available to all users.
Conclusion
Today, we've closed the loop on the request lifecycle by providing clear user feedback during the in-flight state. By leveraging the htmx-request class and the hx-indicator attribute, you can implement a wide range of loading states using only HTML and CSS, a stark contrast to the state management and conditional rendering logic required in a library like React.
Key Takeaways:
- HTMX provides a simple, declarative way to manage loading states by automatically adding the
htmx-requestclass to elements during an AJAX request. - The
hx-indicatorattribute gives you precise control over which element receives thehtmx-requestclass, decoupling the trigger from the indicator. - User feedback is implemented with CSS that shows or hides an indicator based on the presence of the
.htmx-requestclass. - You can implement common patterns like button spinners, content overlays, and skeleton loaders using these core mechanics.
- Accessible indicators are non-negotiable; use ARIA attributes like
aria-busyandrole="status"to inform users of assistive technologies.
In our previous lessons, we built interactivity, managed navigation, and now, added responsive feedback. The final piece of our advanced interactivity module is to enable the server to communicate with the client proactively. In the next lesson, we will explore how to implement real-time updates by connecting to Server-Sent Events (SSE) streams, allowing you to push data from your Express server to the browser without the user needing to initiate a request.
Can't find a good explanation? Sign up and we'll make it for you
Sign up