HTMX Swapping & Transitions — Complete Guide to hx-swap, CSS Animations & OOB Swaps
Master HTMX swapping strategies to build smooth, animated web applications using innerHTML, outerHTML, morphing, OOB swaps, and CSS transitions.
What You’ll Learn
- All
hx-swapstrategies and when to use each one - How to animate content swaps with CSS transitions
- How to use the
.htmx-swappingand.htmx-addedCSS classes - How to update multiple page areas at once with OOB swaps
- How to preserve element state with
hx-preserve - How to use the ViewTransitions API for page-level animations
Why Swapping and Transitions Matter
When you update content on a page without a full reload, the change can feel jarring. Content suddenly appears, disappears, or shifts position. Users perceive this as “janky” — it breaks their flow and reduces trust in the application.
Smooth transitions solve this problem. They guide the user’s eye to the changed content, provide feedback that something happened, and make the app feel polished and professional. Security dashboards like Durga Antivirus Pro use animated transitions to draw attention to new threats without disorienting the analyst monitoring the screen.
Your Learning Path
graph LR
A[HTMX Getting Started] --> B[Requests & Triggers]
B --> C[Swapping & Transitions]
C --> D[Advanced Patterns]
style C fill:#3b82f6,color:#fff,stroke:#2563eb
style A fill:#e2e8f0,stroke:#94a3b8
style B fill:#e2e8f0,stroke:#94a3b8
style D fill:#e2e8f0,stroke:#94a3b8
hx-get, hx-post, hx-target) from the Getting Started guide. Familiarity with CSS transitions and animations is helpful but not required.Understanding hx-swap Strategies
The hx-swap attribute controls how the server’s HTML response is inserted into the page. Think of it like a delivery instruction: “put the new content inside the box” vs “replace the entire box” vs “put it next to the box.”
innerHTML (Default)
Replaces the content inside the target element. The target element itself stays in the DOM.
<!-- The div#content stays; only its inner content is replaced -->
<div id="content" hx-get="/api/update" hx-swap="innerHTML">
Old content here — will be replaced
</div>When to use: Almost always. It’s the safest and most intuitive strategy.
outerHTML
Replaces the target element itself, including its opening and closing tags. The target element is removed and a new element (from the response) takes its place.
<!-- The entire div#card-1 is removed and replaced by the response -->
<div id="card-1" hx-get="/api/new-card" hx-swap="outerHTML">
This card replaces itself entirely
</div>Why use outerHTML? When the container itself needs to change — different CSS class, different tag, or complete replacement. For example, refreshing a widget with a new structure.
Important: After outerHTML, the original element no longer exists. Any event listeners or references to it are lost.
beforebegin / afterbegin / beforeend / afterend
These four strategies insert content relative to the target, rather than replacing its content:
<!-- beforebegin: inserts as a sibling BEFORE the target -->
<div hx-get="/api/item-above" hx-swap="beforebegin">Above</div>
<!-- afterbegin: inserts as the FIRST child of the target -->
<div hx-get="/api/prepend" hx-swap="afterbegin">
<strong>Parent — new content goes before me</strong>
</div>
<!-- beforeend: inserts as the LAST child of the target -->
<div hx-get="/api/append" hx-swap="beforeend">
<strong>Parent — new content goes after me</strong>
</div>
<!-- afterend: inserts as a sibling AFTER the target -->
<div hx-get="/api/item-below" hx-swap="afterend">Below</div>When to use each:
afterbegin— prepend to a list or feedbeforeend— append to a list or feed (most common)beforebegin/afterend— insert adjacent elements like timeline entries
delete and none
<!-- delete: removes the target element from the DOM -->
<button hx-delete="/api/task/5" hx-swap="delete">
Delete Task
</button>
<!-- none: fires the request but does NOT change the DOM -->
<button hx-post="/api/analytics" hx-swap="none">
Record View
</button>Why use none? For analytics pings, log entries, or background operations where you don’t need a visual update.
Swap Strategies Comparison
| Strategy | What Happens | Best Use Case |
|---|---|---|
innerHTML | Replaces content inside target | Default choice for most operations |
outerHTML | Replaces target element itself | Refreshing widgets, changing structure |
beforebegin | Inserts before target (sibling) | Adding items above a list |
afterbegin | Inserts as first child | Prepending to a list |
beforeend | Inserts as last child | Appending to a list |
afterend | Inserts after target (sibling) | Adding items below |
delete | Removes target element | Delete operations |
none | No DOM change | Analytics, logging |
CSS Transitions with HTMX
Now that you know how content gets swapped, let’s make it look good.
The .htmx-swapping Class
When HTMX is about to swap content, it adds the htmx-swapping class to the element being removed. This gives you a chance to animate the old content out before the replacement arrives.
<style>
/* The element starts fully visible */
.card {
transition: opacity 0.3s ease, transform 0.3s ease;
}
/* When HTMX marks it for swapping, it fades out and slides right */
.card.htmx-swapping {
opacity: 0;
transform: translateX(20px);
}
</style>
<div class="card" hx-get="/api/next-card" hx-target="this" hx-swap="outerHTML">
This card fades out before being replaced
</div>Why this works: HTMX doesn’t remove the element immediately. It first applies the htmx-swapping class and waits for CSS transitions to complete (up to the swap timing you specify). The old element animates out, and only then is it replaced by the new one.
The .htmx-added Class
New content receives the htmx-added class after being inserted. This lets you animate it in:
<style>
/* New items fade and slide in from above */
.htmx-added {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
<button hx-get="/api/new-item" hx-target="#list" hx-swap="beforeend">
Add Item
</button>
<ul id="list">
<li>Existing item</li>
</ul>Custom Swap Timing
You can control how long HTMX waits before performing the swap and before “settling” (re-initializing) the new content:
<!-- swap:1s = wait 1 second before swapping (gives old content time to animate out) -->
<div hx-get="/api/data" hx-swap="innerHTML swap:1s">
Old content animates out over 1 second
</div>
<!-- settle:500ms = wait 500ms after swap for new content to initialize -->
<div hx-get="/api/data" hx-swap="innerHTML settle:500ms">
New content settles after 500ms
</div>
<!-- Both custom -->
<div hx-get="/api/data" hx-swap="innerHTML swap:300ms settle:500ms">
Fully custom timing
</div>Why separate swap and settle? The swap phase is for old content leaving. The settle phase is for new content initializing (like re-binding events or starting animations). Separating them gives you fine-grained control.
ViewTransitions API
The modern View Transitions API provides browser-native animated transitions between page states. HTMX supports it with the transition:true modifier.
<style>
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
::view-transition-old(root) {
animation: fade-out 0.3s ease;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease;
}
</style>
<!-- Enable ViewTransitions for this swap -->
<div hx-get="/api/new-content"
hx-target="#main"
hx-swap="innerHTML transition:true">
Navigate with smooth transition
</div>Browser support: ViewTransitions API is supported in Chrome 111+, Edge 111+, and Safari 18+. For older browsers, HTMX gracefully falls back to normal swapping.
Morphing Swaps (Idiomorph)
Regular swaps replace the entire target content. Morphing is smarter — it compares the old and new DOM trees and only changes what’s different.
Why morph? Imagine a list of 100 items and one item’s text changes. With innerHTML, the entire list is rebuilt — losing scroll position, input focus, and animation state. With morphing, only the changed text updates.
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/morph/morph.js"></script>
<div hx-ext="morph">
<div hx-get="/api/update" hx-swap="morphdom" hx-target="#list">
Update List (morph preserves state)
</div>
</div>
<div id="list">
<h2>Current Title</h2>
<p>Only changed elements update.</p>
<input type="text" value="Preserved value">
</div>What morphing preserves:
- Focus and cursor position
- Input values (unless the server sends different values)
- Scroll positions
- Animation state on unchanged elements
Out-of-Band Swaps (hx-swap-oob)
Normally, one request updates one target. OOB (out-of-band) swaps let a single response update multiple elements on the page.
The problem: You submit a comment form. You want to (1) show a success notification, (2) update the comment count, and (3) clear the form. Without OOB, you’d need three separate requests.
The solution: The server includes multiple HTML fragments in one response, each targeting a different page element.
<!-- Server returns this HTML as one response -->
<div id="comments-list">
<!-- New comment appended here -->
<div class="comment">Great article!</div>
</div>
<!-- OOB: updates #notification independently -->
<div id="notification" hx-swap-oob="true">
Comment posted successfully!
</div>
<!-- OOB: updates #comment-count independently -->
<span id="comment-count" hx-swap-oob="innerHTML">
12 comments
</span>On the client side, the existing elements are already in the page:
<!-- These already exist on the page -->
<div id="comments-list">...</div>
<div id="notification"></div>
<span id="comment-count">11 comments</span>HTMX matches elements by id and performs the specified swap on each one.
hx-preserve — Protecting Elements
Some elements should never be replaced — audio players, iframes, or widgets with expensive initialization. hx-preserve protects them:
<div hx-preserve="true" id="music-player">
<audio controls autoplay>
<source src="podcast.mp3" type="audio/mpeg">
</audio>
</div>Why hx-preserve? Without it, swapping the parent container would destroy the audio player, stopping playback. hx-preserve tells HTMX to keep this element (and its internal state) intact across swaps.
Common Mistakes
1. Forgetting to Animate the Leaving Element
Many developers only add animation for new content (.htmx-added) but forget the old content (.htmx-swapping). Without a leaving animation, content suddenly disappears, which feels abrupt.
2. Setting swap Timing Too Short
If your CSS transition takes 500ms but you set swap:100ms, the transition gets cut off. Always match or exceed your CSS transition duration in the swap timing.
3. Using outerHTML When You Mean innerHTML
outerHTML removes the target element from the DOM. If you later try to reference it by ID, it won’t exist. Use innerHTML unless you specifically need to replace the container.
4. OOB ID Mismatches
OOB swaps require exact id matching between the response HTML and the page. An extra space or different case (userId vs user-id) breaks the swap silently.
5. Not Loading the Morph Extension
hx-swap="morphdom" requires the morph extension script. Without it, HTMX silently falls back to innerHTML. Always include the <script> tag for the extension.
6. Animating on Page Load
Elements with transition properties and hx-trigger="load" may animate in unexpectedly when first rendered. Add prefers-reduced-motion support for accessibility.
Practice Questions
Q1: What’s the difference between innerHTML and outerHTML?
A1: innerHTML replaces content inside the target element (the container stays). outerHTML replaces the target element itself (the container is removed and replaced).
Q2: What CSS class does HTMX add to the element being swapped out?
A2: .htmx-swapping — this class is added to the old element before removal, allowing you to animate its exit.
Q3: How do you update multiple page elements with a single HTMX request?
A3: Use OOB (out-of-band) swaps. Add hx-swap-oob="true" to extra elements in the server response. HTMX matches them by id and swaps them independently.
Q4: What does hx-swap="innerHTML swap:500ms settle:300ms" mean?
A4: Wait 500ms before swapping (giving old content time to animate out), then wait 300ms after swap for the new content to settle (initialize). The swap and settle timings are separate.
Q5: When would you use morphing instead of innerHTML?
A5: When you need to preserve DOM state — input values, focus, scroll position — through updates. Morphing only changes elements that differ, keeping everything else intact.
Challenge: Build a dashboard with a list of widgets that auto-refresh every 10 seconds. Use OOB swaps to update individual widgets independently. Add CSS fade-in/out transitions so widgets animate when content changes. Use hx-preserve to protect a clock widget that should never be replaced.
FAQ
Try It Yourself
This sandbox demonstrates animated swapping, OOB updates, and morph-style content replacement.
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| HTMX — Advanced Patterns | WebSockets, SSE, history management, and production deployment |
Related topics: Combine HTMX transitions with Tailwind CSS for utility-first animations, use Alpine.js for client-side interactivity alongside HTMX swaps, and check CSS animation concepts for deeper animation techniques. The swapping patterns you learned here power the animated threat-level dashboards in Durga Antivirus Pro, where smooth transitions help analysts track security events without losing focus.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
What’s Next
Congratulations on completing this Htmx Swapping Transitions tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro