···1+# HTMX + Alpine.js Integration Pattern
2+3+## Problem: "Alpine Expression Error: [variable] is not defined"
4+5+When HTMX swaps in content containing Alpine.js directives (like `x-show`, `x-if`, `@click`), Alpine may not automatically process the new DOM elements, resulting in console errors like:
6+7+```
8+Alpine Expression Error: activeTab is not defined
9+Expression: "activeTab === 'brews'"
10+```
11+12+## Root Cause
13+14+HTMX loads and swaps content into the DOM after Alpine has already initialized. The new elements contain Alpine directives that reference variables in a parent Alpine component's scope, but Alpine doesn't automatically bind these new elements to the existing component.
15+16+## Solution
17+18+Use HTMX's `hx-on::after-swap` event to manually tell Alpine to initialize the new DOM tree:
19+20+```html
21+<div id="content"
22+ hx-get="/api/data"
23+ hx-trigger="load"
24+ hx-swap="innerHTML"
25+ hx-on::after-swap="Alpine.initTree($el)">
26+</div>
27+```
28+29+### Key Points
30+31+- `hx-on::after-swap` - HTMX event that fires after content swap completes
32+- `Alpine.initTree($el)` - Tells Alpine to process all directives in the swapped element
33+- `$el` - HTMX provides this as the target element that received the swap
34+35+## Common Scenario
36+37+**Parent template** (defines Alpine scope):
38+```html
39+<div x-data="{ activeTab: 'brews' }">
40+ <!-- Static content with tab buttons -->
41+ <button @click="activeTab = 'brews'">Brews</button>
42+43+ <!-- HTMX loads dynamic content here -->
44+ <div id="content"
45+ hx-get="/api/tabs"
46+ hx-trigger="load"
47+ hx-swap="innerHTML"
48+ hx-on::after-swap="Alpine.initTree($el)">
49+ </div>
50+</div>
51+```
52+53+**Loaded partial** (uses parent scope):
54+```html
55+<div x-show="activeTab === 'brews'">
56+ <!-- Brew content -->
57+</div>
58+<div x-show="activeTab === 'beans'">
59+ <!-- Bean content -->
60+</div>
61+```
62+63+Without `Alpine.initTree($el)`, the `x-show` directives won't be bound to the parent's `activeTab` variable.
64+65+## Alternative: Alpine Morph Plugin
66+67+For more complex scenarios with nested Alpine components, use the Alpine Morph plugin:
68+69+```html
70+<script src="https://cdn.jsdelivr.net/npm/@alpinejs/morph@3.x.x/dist/cdn.min.js"></script>
71+<div hx-swap="morph"></div>
72+```
73+74+This preserves Alpine state during swaps but requires the plugin.
75+76+## When to Use
77+78+Apply this pattern whenever:
79+1. HTMX loads content containing Alpine directives
80+2. The loaded content references variables from a parent Alpine component
81+3. You see "Expression Error: [variable] is not defined" in console
82+4. Alpine directives in HTMX-loaded content don't work (no reactivity, clicks ignored, etc.)
+2-5
justfile
···1run:
2- @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go
34run-production:
5- @LOG_FORMAT=json SECURE_COOKIES=true go run cmd/server/main.go
6-7-run-firehose:
8- @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go -firehose
910test:
11 @go test ./... -cover -coverprofile=cover.out
···1run:
2+ @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go -firehose
34run-production:
5+ @LOG_FORMAT=json SECURE_COOKIES=true go run cmd/server/main.go -firehose
00067test:
8 @go test ./... -cover -coverprofile=cover.out