Alpine + htmx

Combine Alpine.js for local state with htmx for server round-trips

1 min read 280 words

Overview

This tutorial shows how to use Alpine.js and htmx together: Alpine for dropdowns and modals, htmx for form submission and partial updates.

Setup

Enable Alpine in your config:

config = AppConfig(alpine=True)
app = App(config=config)

Ensure htmx is loaded in your base template (e.g. <script src="https://unpkg.com/htmx.org@2.0.4"></script>).

A filter dropdown that submits via htmx when an option is selected:

{% from "chirp/alpine.html" import dropdown %}

{% call dropdown("Filter by status") %}
  <form hx-get="/tasks" hx-target="#task-list" hx-swap="innerHTML">
    <input type="hidden" name="status" value="open">
    <button type="submit" @click="open = false">Open</button>
  </form>
  <form hx-get="/tasks" hx-target="#task-list" hx-swap="innerHTML">
    <input type="hidden" name="status" value="closed">
    <button type="submit" @click="open = false">Closed</button>
  </form>
{% end %}

<div id="task-list">
  {% for task in tasks %}
    <div class="task">{{ task.title }}</div>
  {% endfor %}
</div>

Alpine controls the dropdown open/close state. htmx handles the filter request and swap.

A "New task" button that opens a modal; the form submits via htmx and closes the modal on success. Usemanaged=false so the parent's openstate controls the modal:

{% from "chirp/alpine.html" import modal %}

<div x-data="{ open: false }">
  <button type="button" @click="open = true">New Task</button>
  {% call modal("new-task-modal", title="New Task", managed=false) %}
    <form hx-post="/tasks" hx-target="#task-list" hx-swap="beforeend"
          @htmx:after-request="open = false">
      <input name="title" required>
      <button type="submit">Create</button>
    </form>
  {% end %}
</div>

<div id="task-list">
  {% for task in tasks %}
    <div class="task">{{ task.title }}</div>
  {% endfor %}
</div>

The parent x-data holds open. The button sets open = true; the form's @htmx:after-request sets open = falseafter a successful submit.

x-cloak for Modals

Add this to your CSS so modals stay hidden until Alpine initializes:

[x-cloak] { display: none !important; }