#
Visual Feedback with hx-indicator
and hx-preserve
In modern web applications, responsiveness is more than just speed, it’s about communication. Users want to know that something is happening the moment they click a button or submit a form. This chapter introduces two powerful tools in the htmx toolbox, hx-indicator
and hx-preserve
, that help your ASP.NET Core Razor Pages applications speak clearly to your users by providing smooth, purposeful visual feedback.
So far, we’ve explored how htmx enhances interactions through hx-get
, hx-post
, and a rich collection of other attributes like hx-target
, hx-trigger
, and hx-swap
. We’ve dynamically loaded content, managed modal dialogs, and even refined our UI components like tabs and tables. Now, we turn to the often-overlooked yet absolutely essential aspect of user experience: providing users with immediate, visible clues that their actions are being processed.
Whether it’s a loading spinner appearing during a long request or maintaining scroll position across updates, htmx gives us tools to craft applications that feel seamless and intuitive. hx-indicator
lets us show activity during requests without manual JavaScript, while hx-preserve
helps us keep parts of the page steady during dynamic changes. These features may not shout for attention, but they can quietly make or break how polished your app feels.
In the next few sections, we’ll walk through how to wire up indicators for various scenarios and when to preserve specific elements to avoid jarring UI transitions. By the end of this chapter, you’ll be able to add the finishing touches that turn functional interactivity into a delightful user experience. Let’s bring your interfaces to life.
#
Loading in Style: Enhancing UX with hx-indicator
When a user interacts with your web app, submitting a form, clicking a button, filtering a table, there’s often a tiny window where nothing seems to happen. No new content, no animation, just silence. In that gap, even if the server is hard at work, users can become uncertain or frustrated. That’s where visual feedback becomes essential. It reassures users that their input was received and that the system is actively processing it. For asynchronous requests triggered by htmx, the hx-indicator
attribute provides a straightforward way to bridge that communication gap.
At its core, hx-indicator
lets you define an HTML element that will automatically receive a CSS class (usually htmx-request) whenever an htmx request is in flight. That means you can toggle loading spinners, fade overlays, or even just disable buttons, all without writing any JavaScript. It’s a simple mechanism that unlocks significant improvements to your app’s perceived performance and usability.
Here’s a basic example. Let’s say you have a search form that queries a database and updates a list of results without reloading the page. You’d like to show a loading spinner next to the search button while the query is running. Here’s how you’d do it:
<form hx-get="/Index?handler=Search" hx-target="#results" hx-indicator="#spinner">
<input type="text" name="query" placeholder="Search users..." />
<button type="submit">Search</button>
<img id="spinner" class="htmx-indicator" src="/img/bars.svg"/>
</form>
<div id="results"></div>
And with a little CSS:
.htmx-indicator{
opacity:0;
transition: opacity 500ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1;
}
.htmx-request.htmx-indicator{
opacity:1;
}
When the user submits the form, htmx automatically adds the htmx-request class to the #spinner
element, making it visible during the request. Once the server responds and the update is complete, the class is removed and the spinner fades away. This approach keeps your UI responsive and your users informed, without a single line of JavaScript.
You can also apply hx-indicator
to more than just inline spinners. Want to block an entire section while loading? Wrap your content in a container with an overlay and trigger it using hx-indicator
. For example, on a table that updates dynamically, you could show a semi-transparent overlay:
<div class="table-container" hx-indicator="#table-loader">
<div class="overlay visually-hidden" id="table-loader">Loading...</div>
<table>
<!-- rows rendered here -->
</table>
</div>
This scales beautifully to larger components like modals, card lists, or any area where you need to signal that something is happening. And because the attribute can target any CSS selector, you can define very specific and context-aware indicators without duplicating logic across your app.
For an even smoother user experience, consider pairing hx-indicator
with hx-target
and hx-swap
for fine-tuned updates. For instance, as a user types into a search box, you can debounce the requests and still show a subtle spinner, keeping the UI lively without being overwhelming.
Visual feedback might seem like a small detail, but in fast, interactive applications, it’s what separates “feels broken” from “feels polished.” With hx-indicator
, you’ve got an elegant, server-driven way to let your users know the system is responsive and attentive to their actions. It's another example of htmx and Razor Pages working together to keep things simple, powerful, and user-friendly.
#
Maintaining State Between Updates with hx-preserve
One of the subtle challenges in building interactive web apps with htmx is handling what happens after a partial update. When htmx replaces content inside a div or a section, it swaps out the HTML as if the user never touched it. That works great for displaying fresh content, but it can be jarring and even frustrating when it erases something the user was in the middle of doing. Think of a partially filled form that gets refreshed after an update, or a toggle switch that resets unexpectedly. It’s a small issue that can quickly turn into a big annoyance.
That’s where hx-preserve
comes in. It tells htmx: "Hey, if you’re about to replace this element with something new, please keep the original version if nothing has changed structurally." In other words, hx-preserve
helps you maintain a user's place or input in the UI while still letting the server send back fresh content. It works by tracking DOM elements with the id attribute and comparing them during the update.
Here’s a practical scenario. Let’s say you have a multi-field search form that updates a results list when submitted. If the server returns a refreshed version of the form (maybe with a validation message or updated placeholder), you don't want it to clear out everything the user just typed. By adding hx-preserve
to the form inputs, htmx will keep their current values intact during the update.
<form asp-page-handler="Search" hx-post="/Index?handler=Search" hx-target="#results">
<input id="query" name="query" type="text" placeholder="Search..." hx-preserve />
<select id="filter" name="filter" hx-preserve>
<option value="all">All</option>
<option value="active">Active</option>
<option value="archived">Archived</option>
</select>
<button type="submit">Go</button>
</form>
<div id="results"></div>
In this case, even if your Razor Page returns a refreshed