Alpine Compat

The alpine-compat extension preserves Alpine.js

state across htmx swaps, re-runs Alpine init on swapped-in

content, and round-trips Alpine component state through

htmx's history cache. It ships inside

/public/htmgo.js and auto-gates on

window.Alpine — if you don't load Alpine, the

extension no-ops.

Loading Alpine alongside htmgo:

Copy
 1h.Html(
 2    h.Head(
 3        // Alpine v3.15.11 — tested pin. Use [email protected] for patch updates.
 4        h.Tag("script",
 5            h.Attribute("src", "https://unpkg.com/[email protected]/dist/cdn.min.js"),
 6            h.Attribute("defer", ""),
 7        ),
 8        h.Script("/public/htmgo.js"),
 9    ),
10)
The 'defer' attribute is important — Alpine auto-initializes on DOMContentLoaded. htmgo.js can be loaded before or after Alpine; extensions self-register in htmx 4.

Prevent FOUC for x-cloak:

Add this CSS rule so x-cloak-marked elements stay hidden until Alpine initializes them:

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

Using the ax package:

htmgo's framework/ax package mirrors the

hx shape with Alpine directive helpers. Common

helpers: ax.Data, ax.Show,

ax.OnClick, ax.Model,

ax.BindClass, ax.OnKeydownEscape,

ax.Cloak. See GoDoc for the full list.

Copy
 1h.Div(
 2    h.Class("relative"),
 3    ax.Data("{ open: false }"),
 4
 5    h.Button(
 6        h.Class("btn"),
 7        ax.OnClick("open = !open"),
 8        h.Text("Toggle"),
 9    ),
10
11    h.Div(
12        h.Class("popover"),
13        ax.Show("open"),
14        ax.Cloak(),
15        ax.OnClickOutside("open = false"),
16        ax.OnKeydownEscape("open = false"),
17        h.HxGet("/popover/content"),
18        h.HxTrigger("intersect once"),
19        h.HxSwap(hx.SwapTypeInnerHtml),
20        h.Text("Loading…"),
21    ),
22)

Alpine + htmx swap interaction:

When htmx morphs content into an Alpine component's

descendant, the extension's

htmx_before_morph_node hook copies

_x_dataStack to the new nodes and

Alpine.cloneNode preserves bindings. State

survives the swap. When htmx morphs the Alpine root itself,

the extension still carries state through because it runs

before htmx's morph op.

Gotchas:

• Alpine v3 only — v2 is out of support upstream. • Tested against Alpine v3.15.11 (released 2025-04-02). Patch updates within v3.15 should be safe; 3.16 is not on the public roadmap. • If Alpine loads after a swap, pre-Alpine widgets won't init until Alpine boots. Load Alpine in the page with 'defer'. • Alpine plugins (@alpinejs/persist, @alpinejs/intersect, etc.) must be loaded before Alpine itself, per the plugin docs.