HTMX CSS Animations
HTMX adds and removes CSS classes at each stage of the request lifecycle. You write CSS rules that respond to those classes, and the result is smooth animations — fade-ins, slide-ins, highlights, and fade-outs — without writing a single line of animation JavaScript. The animations run entirely in CSS, which means they run on the GPU and stay smooth even on slower devices.
The HTMX Lifecycle CSS Classes
| Class | Added To | When | Removed |
|---|---|---|---|
| htmx-request | Trigger element | Request is in flight | Response received |
| htmx-swapping | Target element | About to swap content | After swap delay |
| htmx-settling | Target element | After swap, during settle period | After settle delay |
| htmx-added | New content element | Just inserted into the DOM | After settling |
Fade-In New Content
When HTMX adds new content, it temporarily applies htmx-added to the inserted element. Use this class to start the element at opacity 0 and transition to 1:
<style>
.htmx-added {
opacity: 0;
transition: opacity 0.4s ease;
}
.htmx-settling {
opacity: 1;
}
</style>
<ul id="task-list"></ul>
<form hx-post="/tasks" hx-target="#task-list" hx-swap="beforeend settle:300ms">
<input type="text" name="title" placeholder="New task">
<button type="submit">Add</button>
</form>
Animation timeline:
New <li> inserted → htmx-added applied → opacity: 0
↓ (next render frame)
htmx-settling applied → opacity transitions to 1 over 400ms
↓
Classes removed → element stays at opacity: 1
Fade-Out Before Swap
To animate the old content out before the new content appears, use the swap timing modifier to delay the swap and animate with the htmx-swapping class:
<style>
.htmx-swapping {
opacity: 0;
transition: opacity 0.3s ease;
}
</style>
<div id="panel"
hx-get="/new-content"
hx-trigger="click"
hx-swap="innerHTML swap:300ms">
Old content here
</div>
Sequence:
Click fires request
↓
htmx-swapping added → panel fades to opacity 0 over 300ms
↓ (after 300ms)
New content swapped in → htmx-swapping removed
↓
New content starts at full opacity (or fades in with htmx-added)
Slide-Down New List Items
<style>
@keyframes slideDown {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.htmx-added li {
animation: slideDown 0.3s ease forwards;
}
</style>
<ul id="comments"></ul>
Each new <li> appended by HTMX slides down from 20px above its final position and fades in simultaneously.
Highlight a Row After Update
A yellow flash on a row that was just updated is a common UX pattern. It tells the user which row changed without a full page refresh:
<style>
@keyframes yellowFlash {
0% { background-color: #fff3cd; }
100% { background-color: transparent; }
}
.htmx-settling tr.updated-row {
animation: yellowFlash 1.5s ease forwards;
}
</style>
Server returns the updated row with the class added:
<tr id="row-7" class="updated-row"> <td>Item Seven</td> <td>$49.99</td> </tr>
Skeleton Loading Animation
Show an animated skeleton placeholder while content loads, then replace it with the real content on response:
<style>
.skeleton-line {
height: 16px;
background: linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%);
background-size: 200% 100%;
animation: shimmer 1.2s infinite;
border-radius: 4px;
margin-bottom: 10px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
<div hx-get="/article/5" hx-trigger="load" hx-target="this" hx-swap="outerHTML">
<div class="skeleton-line" style="width:70%"></div>
<div class="skeleton-line" style="width:100%"></div>
<div class="skeleton-line" style="width:85%"></div>
</div>
Button State Animation During Request
<style>
button.htmx-request {
opacity: 0.6;
cursor: not-allowed;
position: relative;
}
button.htmx-request::after {
content: "";
display: inline-block;
width: 12px;
height: 12px;
margin-left: 8px;
border: 2px solid #fff;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.6s linear infinite;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<button hx-post="/save" hx-target="#result">Save</button>
While the request is in flight, the button dims and a spinning indicator appears after the button text — entirely through CSS.
Controlling Timing With Swap Modifiers
hx-swap="innerHTML swap:300ms settle:400ms"
- swap:300ms — Wait 300ms before inserting new content (gives the fade-out animation time to complete).
- settle:400ms — Keep the
htmx-settlingclass for 400ms after insertion (gives the fade-in animation time to complete).
Coordinate these values with your CSS transition duration for smooth, glitch-free animations.
Key Takeaway
HTMX CSS animations work by targeting the lifecycle classes HTMX applies during requests: htmx-request, htmx-swapping, htmx-settling, and htmx-added. Write CSS transitions or keyframe animations triggered by these classes. Use swap and settle timing modifiers in hx-swap to coordinate animation duration with content replacement timing. The result is smooth, GPU-accelerated page transitions with no animation JavaScript required.
