HTMX Indicators — Complete Guide with Examples
In this tutorial, you'll learn about HTMX indicators. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
HTMX indicators provide visual feedback during AJAX requests, using CSS class toggling and the hx-indicator attribute to show loading states, spinners, and progress animations.
What You'll Learn
By the end of this tutorial, you'll use hx-indicator for custom loading elements, style the htmx-request class, create CSS-only loading animations, build progress bars, and combine indicators with triggers.
Why It Matters
Users need to know their action is being processed. Without indicators, they may click again, navigate away, or think the site is broken. Good indicators improve perceived performance and reduce user frustration.
Real-World Use
Durga Antivirus Pro's scan button shows a pulsing indicator during file analysis. The button text changes to "Scanning..." and a spinner appears next to it. When complete, the indicator disappears and results appear.
Where This Fits in Your Learning Path
flowchart LR
A["History Management"] --> B["**Indicators & UX**"]
B --> C["Hyperscript"]
C --> D["Extensions"]
D --> E["Advanced HTMX"]
style B fill:#3b82f6,stroke:#2563eb,color:#fff
style A fill:#e2e8f0,stroke:#94a3b8
style E fill:#e2e8f0,stroke:#94a3b8
Basic Indicator with hx-indicator
Point to an element that shows/hides during the request.
<button hx-get="/api/slow-data"
hx-target="#result"
hx-indicator="#spinner">
Load Data
</button>
<img id="spinner" class="htmx-indicator" src="/spinner.gif" alt="Loading..." style="display:none">
Expected output: The spinner is hidden by default. During the request, it becomes visible. When complete, it hides again.
Using the htmx-request CSS Class
The triggering element gets the .htmx-request class during the request.
<button hx-post="/api/submit" class="btn-submit">
<span class="btn-text">Submit</span>
<span class="btn-spinner htmx-indicator">Loading...</span>
</button>
<style>
.btn-submit .btn-spinner { display: none; }
.btn-submit.htmx-request .btn-text { display: none; }
.btn-submit.htmx-request .btn-spinner { display: inline; }
</style>
Expected output: The button text is replaced by "Loading..." during the request. The htmx-request class toggles visibility.
CSS-Only Spinner Animation
Create a spinner without external images.
<button hx-get="/api/data" hx-indicator="#css-spinner">
Load
</button>
<div id="css-spinner" class="htmx-indicator spinner"></div>
<style>
.spinner {
display: none;
width: 24px;
height: 24px;
border: 3px solid #e0e0e0;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.spinner.htmx-request { display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
Expected output: A rotating CSS spinner appears during the request and disappears when complete.
Progress Bar Indicator
Show a horizontal progress bar during requests.
<div hx-post="/api/upload"
hx-indicator="#progress-container">
<button type="submit">Upload</button>
</div>
<div id="progress-container" class="htmx-indicator progress-bar" style="display:none">
<div class="progress-fill"></div>
</div>
<style>
.progress-bar {
width: 100%;
height: 6px;
background: #e0e0e0;
border-radius: 3px;
overflow: hidden;
margin-top: 8px;
}
.progress-fill {
height: 100%;
background: #3b82f6;
width: 30%;
border-radius: 3px;
animation: progress-indeterminate 2s ease-in-out infinite;
}
.progress-bar.htmx-request { display: block; }
@keyframes progress-indeterminate {
0% { transform: translateX(-100%); }
100% { transform: translateX(400%); }
}
</style>
Expected output: An animated indeterminate progress bar appears during the request.
Multiple Indicators
Use multiple indicators for different elements.
<div hx-get="/api/dashboard"
hx-target="#dashboard"
hx-indicator=".loading-spinner">
<button class="refresh-btn">Refresh Dashboard</button>
</div>
<div id="dashboard">
<div class="loading-spinner htmx-indicator" style="display:none">
Loading dashboard...
</div>
Dashboard content here
</div>
Expected output: Multiple elements can share or have separate indicators. The indicator classes control visibility.
Disabling Elements During Request
Use htmx-request class to disable buttons visually and functionally.
<button hx-post="/api/submit-order"
class="submit-btn"
aria-busy="false">
Place Order
</button>
<style>
.submit-btn.htmx-request {
opacity: 0.6;
pointer-events: none;
cursor: not-allowed;
}
.submit-btn.htmx-request::after {
content: '...';
}
</style>
Expected output: The button appears disabled during the request, with reduced opacity and a "..." suffix.
Common Mistakes
1. Forgetting the .htmx-indicator class
HTMX looks for the .htmx-indicator class to show/hide elements. Without it, the indicator remains visible.
2. Using display:none with CSS animations
display:none prevents animations. Use opacity or visibility for animated indicators.
3. Not resetting indicator state on error
If a request fails, the indicator remains visible. HTMX handles this automatically, but custom error handling should also hide indicators.
4. Overusing indicators for fast requests
Requests under 200ms don't need indicators. They flash and annoy users. Add a minimum display delay.
5. Creating too many simultaneous indicators
Multiple indicators on the same page can be distracting. Use one global indicator for non-critical requests.
Practice Questions
What attribute shows an indicator during HTMX requests? hx-indicator with a CSS selector pointing to the indicator element.
What CSS class does HTMX add during a request? The .htmx-request class is added to the triggering element.
What CSS class must an indicator have? The .htmx-indicator class for HTMX to manage its visibility.
How do you create a CSS-only spinner? Use a div with a border, border-top-color, border-radius: 50%, and a rotation animation.
Can you disable a button during an HTMX request? Yes. Use CSS on .htmx-request to set pointer-events: none and reduced opacity.
Challenge
Build a submit button that shows an inline spinner, disables itself, changes text to "Saving...", and shows a checkmark when complete using CSS transitions.
FAQ
Mini Project
Build a complete loading UI system with three indicator types: a button with inline spinner, a progress bar for the page, and a toast notification for background operations.
<style>
.btn-loading { position: relative; }
.btn-loading .spinner { display: none; width: 16px; height: 16px; border: 2px solid #fff; border-top-color: transparent; border-radius: 50%; animation: spin 0.6s linear infinite; margin-right: 8px; vertical-align: middle; }
.btn-loading.htmx-request .spinner { display: inline-block; }
.btn-loading.htmx-request .btn-label { display: none; }
.page-loader { position: fixed; top: 0; left: 0; width: 100%; height: 3px; z-index: 9999; }
.page-loader .fill { height: 100%; background: linear-gradient(90deg, #3b82f6, #8b5cf6); animation: load 2s ease-in-out infinite; width: 30%; border-radius: 3px; transform-origin: left; }
.page-loader.htmx-request .fill { animation: load 1.5s ease-in-out infinite; }
.page-loader:not(.htmx-request) .fill { width: 0; animation: none; transition: width 0.3s; }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes load { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } }
</style>
<div class="page-loader htmx-indicator"><div class="fill"></div></div>
<button hx-post="/api/save" hx-target="#result" class="btn-loading">
<span class="spinner htmx-indicator"></span>
<span class="btn-label">Save Changes</span>
</button>
<div id="result"></div>
What's Next
Learn about Hyperscript:
| Tutorial | What You'll Learn |
|---|---|
| Hyperscript | Enhance HTMX behavior with Hyperscript |
| HTMX Extensions | Extend HTMX with community extensions |
Related topics: CSS animations and transitions, CSS class toggling techniques.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro