Back to Home

The AlpineJS x HTMX Nightmare

February 2025

TLDR

HTMX and Alpine both try to control the DOM. They’re not aware of each other. They don’t communicate. They don’t share state. HTMX blindly replaces chunks. Alpine expects its bindings to stay intact.

If you want your project to stay sane:

  • Use Alpine for isolated, static components
  • Use HTMX for backend-rendered partials and avoid frontend state inside them
  • Avoid using them inside the same component unless you love debugging in silence at 3am

When I first found AlpineJS and HTMX, it felt like I hit the jackpot. Alpine gave me Vue-style reactivity without the bloat. HTMX promised server-rendered Ajax without touching JavaScript. On paper, it looked like the perfect minimalist frontend stack.

But once I started mixing them in a dynamic table, I realized I had walked into a burning building with gasoline in both hands.

You think it’s just sprinkle-some-HTMX-for-Ajax and toss-in-Alpine-for-interactivity, right? Nah. These two tools do not vibe together when you’re doing anything beyond a static counter. Especially not when HTMX is swapping HTML fragments and Alpine is trying to manage frontend state in the same space.

Where It All Goes to Hell

Let me walk you through the pain points one by one. If you’ve ever used Alpine inside a table that gets updated via HTMX, you’ve probably screamed into the void too.

1. Alpine gets wiped out after an HTMX request
HTMX replaces chunks of your DOM. Alpine doesn’t automatically reinitialize. That means your dropdowns, modals, and inline logic just vanish.
You need to hook into the htmx:afterSwap event and manually call Alpine.initTree(el). Which feels cursed because the whole point of Alpine was not writing JavaScript boilerplate.

2. x-data state gets erased
If you're tracking open/close states or input values with Alpine, and HTMX swaps out that portion of the DOM, poof... it’s gone.
No reactivity. No warning. Just silence.

3. Dropdowns and x-show randomly stop working
One second your dropdown is toggled. The next, the DOM has been swapped mid-click, and your Alpine code doesn’t know it should exist anymore.
Good luck debugging that flicker.

4. x-effect and x-init stop running
Alpine doesn’t re-run x-init or x-effect after HTMX swaps content in. You think it’s reactive. But nothing fires.
You’ll wonder why your code isn’t working until you remember you forgot to manually tell Alpine to wake up again.

5. Nested Alpine logic breaks completely
HTMX updates a parent container and Alpine just ignores the fresh children. The new nodes sit there dead.
You’ll have to manually walk the DOM and bring them to life. Frankenstein style.

6. State flickers like crazy during HTMX updates
When Alpine manages UI state and HTMX updates the DOM, the result is often ugly. Buttons reset. Dropdowns flicker. Inputs lose their values. It’s like fighting your own UI.

7. Code turns into unholy spaghetti
Once you start patching the cracks, your simple HTMX-Alpine combo becomes a pile of custom event listeners, Alpine.initTree() hacks, and conditional rendering weirdness.
At that point, you might as well switch to Vue or React and spare yourself the brain damage.

What You Should Do Instead

If you're already deep into Alpine but want some Ajax action, use alpine-ajax. It’s a small Alpine plugin that lets you send requests and update parts of the DOM without nuking your reactivity.

Or if you enjoy pain and want to go off the deep end, try _hyperscript. It’s like writing incantations to manipulate the DOM. Weird syntax, but some folks love it.

But for real, if you want clean stateful interactivity and Ajax requests in the same zone, either:

  • Use HTMX and keep all logic backend-driven
  • Use Alpine for isolated UI parts that never get swapped by HTMX
  • Or just choose one and stick with it

Mixing them in one component is like playing Jenga during an earthquake.

Real Example That Will Break Your Soul

Here’s a classic scenario. You have a table that loads pages via HTMX, and each row has Alpine-powered interactivity.

<tbody hx-get="/items?page=2" hx-target="this" hx-swap="outerHTML">
<tr x-data="{ open: false }">
<td @click="open = !open">
<span x-text="open ? 'Hide' : 'Show'"></span>
</td>
</tr>
</tbody>

You click the row. Alpine flips the state. You paginate. HTMX replaces the DOM. Alpine state gone. Alpine bindings gone.
No warning. No error. Just vibes and broken UI.

Final Thoughts

HTMX is amazing. Alpine is amazing. But together? They’re like two geniuses in a group project who refuse to talk to each other.

If you're building interactive tables, modals, or anything reactive that swaps, this combo will make you want to throw your laptop into the ocean.

Choose wisely. Or embrace the chaos.

The AlpineJS x HTMX Nightmare