···11+# HTMX + Alpine.js Integration Pattern
22+33+## Problem: "Alpine Expression Error: [variable] is not defined"
44+55+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:
66+77+```
88+Alpine Expression Error: activeTab is not defined
99+Expression: "activeTab === 'brews'"
1010+```
1111+1212+## Root Cause
1313+1414+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.
1515+1616+## Solution
1717+1818+Use HTMX's `hx-on::after-swap` event to manually tell Alpine to initialize the new DOM tree:
1919+2020+```html
2121+<div id="content"
2222+ hx-get="/api/data"
2323+ hx-trigger="load"
2424+ hx-swap="innerHTML"
2525+ hx-on::after-swap="Alpine.initTree($el)">
2626+</div>
2727+```
2828+2929+### Key Points
3030+3131+- `hx-on::after-swap` - HTMX event that fires after content swap completes
3232+- `Alpine.initTree($el)` - Tells Alpine to process all directives in the swapped element
3333+- `$el` - HTMX provides this as the target element that received the swap
3434+3535+## Common Scenario
3636+3737+**Parent template** (defines Alpine scope):
3838+```html
3939+<div x-data="{ activeTab: 'brews' }">
4040+ <!-- Static content with tab buttons -->
4141+ <button @click="activeTab = 'brews'">Brews</button>
4242+4343+ <!-- HTMX loads dynamic content here -->
4444+ <div id="content"
4545+ hx-get="/api/tabs"
4646+ hx-trigger="load"
4747+ hx-swap="innerHTML"
4848+ hx-on::after-swap="Alpine.initTree($el)">
4949+ </div>
5050+</div>
5151+```
5252+5353+**Loaded partial** (uses parent scope):
5454+```html
5555+<div x-show="activeTab === 'brews'">
5656+ <!-- Brew content -->
5757+</div>
5858+<div x-show="activeTab === 'beans'">
5959+ <!-- Bean content -->
6060+</div>
6161+```
6262+6363+Without `Alpine.initTree($el)`, the `x-show` directives won't be bound to the parent's `activeTab` variable.
6464+6565+## Alternative: Alpine Morph Plugin
6666+6767+For more complex scenarios with nested Alpine components, use the Alpine Morph plugin:
6868+6969+```html
7070+<script src="https://cdn.jsdelivr.net/npm/@alpinejs/morph@3.x.x/dist/cdn.min.js"></script>
7171+<div hx-swap="morph"></div>
7272+```
7373+7474+This preserves Alpine state during swaps but requires the plugin.
7575+7676+## When to Use
7777+7878+Apply this pattern whenever:
7979+1. HTMX loads content containing Alpine directives
8080+2. The loaded content references variables from a parent Alpine component
8181+3. You see "Expression Error: [variable] is not defined" in console
8282+4. Alpine directives in HTMX-loaded content don't work (no reactivity, clicks ignored, etc.)
+2-5
justfile
···11run:
22- @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go
22+ @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go -firehose
3344run-production:
55- @LOG_FORMAT=json SECURE_COOKIES=true go run cmd/server/main.go
66-77-run-firehose:
88- @LOG_LEVEL=debug LOG_FORMAT=console go run cmd/server/main.go -firehose
55+ @LOG_FORMAT=json SECURE_COOKIES=true go run cmd/server/main.go -firehose
96107test:
118 @go test ./... -cover -coverprofile=cover.out