Matthew Tyson
Contributing Writer

HTMX and Alpine.js: How to combine two great, lean front ends

how-to
Apr 2, 20259 mins
Development Libraries and FrameworksJavaScriptWeb Development

Alpine and HTMX both take a fresh approach to web development by pushing the limits of HTML Now you can use them together.

Person on snow skiis, mid leap in flurries of snow.
Credit: Dmitry Molchanov / Shutterstock

HTMX and Alpine are two low-overhead ways to empower your front end. Neither requires a build step, and both let you add Ajax-style API calls and client-side UI updates with minimal fuss. While the two technologies differ in approach, neither gets in its own way (or yours), and you can use them together. Whatโ€™s not to like?

Overview of Alpine and HTMX

Both HTMX and Alpine are founded on a core idea, and both are admirably focused on that one central mission. For HTMX, the mission could be summarized as: Make the web follow true RESTful design by allowing HTML to do basic Ajax and fine-grained view updates with HTML attributes. For Alpine, itโ€™s: Put state and reactivity into a tiny package that can fit into HTML attributes.

Itโ€™s clear from these statements what the frameworks have in common: They both use HTML attributes as their key mechanism. In my opinion, this is an excellent approach because HTML is so ubiquitous in web development. At the end of the day, youโ€™ll always have to write some HTML (or HTML-like JSX, or something like it). HTML is the structure of the UI, and by decorating it with the behavior you need, you avoid introducing anything extra to think about and manage.

What about the separation of concerns?

A critique of both HTMX and Alpine is that their closeness to HTML violates the separation of concerns, which encourages separating the view from the behavior. I donโ€™t have beef with this in principle, especially for larger, more heavily architected apps. But in the case of HTMX, I donโ€™t think the criticism holds much weight because itโ€™s really just an extension of HTML. (Besides, as Iโ€™ve said before, I believe itโ€™s only a matter of time before HTMX is adopted into HTML.)

Alpine also appears to be something of an extension of HTML. Except, in the case of Alpine, you are dealing directly with JavaScript, which is contained within the HTML attributes. In this case, your HTML knows how to speak JavaScript.

The deciding question here is whether the tool meets your projectโ€™s needs. If your key requirement is to get Ajax and some modest dynamic UI interactions going with as little overhead as possible, you really owe it to yourself to consider both of these libraries.

Using Alpine and HTMX together

Before I stop rapping, and we start looking at code, I should note that you can use Alpine and HTMX together. Consider, for example, using HTMX for what it does bestโ€”driving the data from the back endโ€”while letting Alpine handle client-side enhancements (like dropdowns, accordions, and client-side filtering). You could load list data with HTMX and then filter it with Alpine, for example. Combining them also opens the possibility of using HTMX for the simpler Ajax interactions, then dropping into Alpine for more elaborate ones (in cases where HTMX alone isnโ€™t convenient to transform the data on the client).

If you think about HTMX as an extension of HTML, then the combination makes perfect sense: You are basically using HTML with an extension, then adding Alpine for some extra power within a small footprint.

Whereโ€™s the state?

In HTMX, state lives on the server. HTMX gets chunks of HTML representing that state from the server. (This is actually the essence of REST and HATEOAS, worthy side trails to explore in deepening an understanding of how the web works.) HTMX uses attributes that initiate background (Ajax) requests and then updates the UI with the response. Hereโ€™s a simple example of using x-get:


<div>
  <ul id="album-list"></ul> 
  <button hx-get="/albums" hx-target="#album-list">Load Albums</button> 
</div>

In this case, the response could look something like this:


<li>Get Up, Stand Up</li>
<li>Wildflowers</li>
<li>To the Sea</li>

When that arrives at the client, HTMX will put it into position, giving you a server-driven view of the state.

In Alpine, you might also have state on the server, but you have state on the client, as well. Hereโ€™s how the same example would work using a typical Alpine approach:


<div x-data="{ 
  albums: [],
  fetchAlbums() {
    fetch('/albums')
    .then(response => response.json())
    .then(data => {
        this.albums = data;
      });
  }
}">
  <ul>
    <template x-for="album in albums":key="album.id">
      <li x-text="album.name"></li>
    </template>
  </ul>
  <button @click="fetchAlbums()">Load Albums</button>
</div>

For this one, x-data is the attribute that creates the Alpine component and it defines the necessary client-side state (albums) and a bespoke method for fetching the data from the server (fetchAlbums). Notice the view inside the unordered list (the <ul>) uses some further Alpine attributes (x-for and x-text) to perform an iteration and describe how to output the state to the screen.

The state is held in x-data attributes and Alpineโ€™s job is to take that state and ensure the UI reflects it automatically. The data from the server, as soon as it is received by Alpineโ€™s fetch call, will be rendered by the view. Hereโ€™s what the data coming back for this example might look like:


[ 
  { "id": 1, "name": "The Dark Side of the Moon" }, 
  { "id": 2, "name": "Kind of Blue" }, 
  { "id": 3, "name": "Abbey Road" }, 
  { "id": 4, "name": "Rumours" }
]

When we compare HTMX data with Alpineโ€™s, the difference between REST (where the representation of the state, meaning the view, is transferred) and REST-like (as commonly implemented with JSON APIs) is very clear. HTMX wants to send chunks of the actual view containing the state, and Alpine wants to send chunks of a data format (JSON) and then transform it into a view on the client.

There is no clear right way or โ€œwinnerโ€ here. But HTMX does have one huge advantage, which is its amazing client-side simplicity. For its part, Alpine offers a high degree of simplicity along with great flexibility. Because Alpineโ€™s attributes are actually JavaScript, you can do anything in JavaScript in Alpine. (Thereโ€™s also nothing to prevent you from pulling the JavaScript into a script tag if it becomes cumbersome being inlined in the HTML).

When to combine HTMX and Alpine

HTMX wants to be the simplest possible way to use HTML to interact with the server, and it does that very well. It is also remarkably flexible, so donโ€™t be too quick to assume that some requirement is not within HTMXโ€™s bailiwick. Check the HTMX demo page for a set of examples you might not initially think it could handle, including things like infinite scroll, auto-complete input, and client- and server-side field validation. (Thereโ€™s even a WebSocket extension.)

Of course, the more elaborate your UI interaction, the more complex things become; thatโ€™s just the nature of the beast. So, letโ€™s now consider a scenario where HTMX might fall short and where we could incorporate Alpine. You could use a variety of approaches to enhance HTMXโ€™s core abilities, including vanilla JavaScript and its sibling project, Hyperscript, but Alpine makes an excellent choice.

One obvious use case for Alpine on top of HTMX is if you need to fancy up some client-side code:


<!DOCTYPE html>
<html>
<head>
    <title>Combined Example</title>
    <script src="https://unpkg.com/htmx.org@1.9.10"></script>
    <script src="https://unpkg.com/alpinejs@3.x.x" defer></script>
</head>
<body>
<div x-data="{ 
        selectedProduct: null,
        showDetails: false,
        selectProduct(event) {
          console.log(event.target.innerHTML);
            this.selectedProduct = event.target.innerHTML;
            this.showDetails = true;
        }
    }" @click="selectProduct">
    <div id="product-list"></div>
    <div class="product" x-show="showDetails">
        <p x-text="selectedProduct"></p>
    </div>
    </div>
    <select hx-get="/products" name="category" hx-target="#product-list">
        <option value="all">All</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
    </select>

</body>
</html>

In this example, we use HTMX to load products based on the selected category. This is a kind of server-side filtering, which HTMX can do all day long, like a champ. But once the products are loaded, we might want to use Alpine to support showing and hiding the product details.

In this particular example, notice that we attach the click handler to the div instead of the individual product items. Those items were sent from the server, and weโ€™d rather not have our client-side Alpine click handler generated on the server because we want to keep our code clean. So our handler pulls the clicked item and uses Alpineโ€™s awesome client-side state support to dynamically show and populate a product detail pane. In this example, we just do a simple event.target.innerHTML to get the product details. Thereโ€™s a lot more we could do with this sort of combination, leveraging the strength of each tool.

Using Alpine for JSON data

For a more involved example of using Alpine and HTMX together, think about a case where you need to transform some data upon submission. HTMX is devoted to using HTML-standard data formats, but what if you have an API that wants to consume JSON?

HTMX does have a JSON extension that lets you deal in JSON calls, but if you already are using Alpine, you might want to stick with that. Thereโ€™s no reason you canโ€™t use HTMX on all those endpoints that can speak HTML, and then drop into Alpineโ€™s fetch mechanism for the JSON endpoints. (See the Alpine.js pages to learn more about data fetching.)

Conclusion

Itโ€™s no secret that Iโ€™m a fan of both HTMX and Alpine. There is something Iโ€™ll call the โ€œweight-to-capabilityโ€ ratio that goes into software libraries. The ratio is basically how much detail you have to add to your project and brain versus how much you can do with it.

Both HTMX and Alpine are real champions on the weight-to-capability front. If you are open to using a back end that talks HTML, HTMX is a no-brainer. If you need a clean, powerful reactive framework, Alpine is a clear choice. And if you need both, well then, you can just use them together.

Matthew Tyson
Contributing Writer

Matthew Tyson is a contributing writer at InfoWorld. A seasoned technology journalist and expert in enterprise software development, Matthew has written about programming, programming languages, language frameworks, application platforms, development tools, databases, cryptography, information security, cloud computing, and emerging technologies such as blockchain and machine learning for more than 15 years. His work has appeared in leading publications including InfoWorld, CIO, CSO Online, and IBM developerWorks. Matthew also has had the privilege of interviewing many tech luminaries including Brendan Eich, Grady Booch, Guillermo Rauch, and Martin Hellman.

Matthewโ€™s diverse background encompasses full-stack development (Java, JVM languages such as Kotlin, JavaScript, Python, .NET), front-end development (Angular, React, Vue, Svelte) and back-end development (Spring Boot, Node.js, Django), software architecture, and IT infrastructure at companies ranging from startups to Fortune 500 enterprises. He is a trusted authority in critical technology areas such as database design (SQL and NoSQL), AI-assisted coding, agentic AI, open-source initiatives, enterprise integration, and cloud platforms, providing insightful analysis and practical guidance rooted in real-world experience.

More from this author