Skip to main content

Alpine.js Patterns

Combine server-rendered Luat templates with Alpine.js for lightweight client-side interactivity.

Why Luat + Alpine? Luat handles server-side rendering with full Lua power, while Alpine.js adds reactive behavior without a build step. Together they create fast, interactive pages with minimal JavaScript.

Setup

CDN

Add Alpine.js to your app.html before the closing </body> tag:

<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

npm

npm install alpinejs
// assets/js/app.ts
import Alpine from 'alpinejs';

window.Alpine = Alpine;
Alpine.start();

Using with HTMX

When using Alpine.js together with HTMX, Alpine components in swapped content need to be reinitialized. Alpine uses a MutationObserver that automatically detects DOM changes, but for reliable initialization during HTMX swaps, use the deferred mutations pattern:

// assets/js/app.ts
import Alpine from 'alpinejs';
import htmx from 'htmx.org';

// Pause Alpine during HTMX swaps, resume after DOM settles
document.addEventListener('htmx:beforeSwap', () => {
Alpine.deferMutations();
});

document.addEventListener('htmx:afterSettle', () => {
Alpine.flushAndStopDeferringMutations();
});

window.Alpine = Alpine;
window.htmx = htmx;
Alpine.start();

This pauses Alpine's reactivity before HTMX modifies the DOM and resumes it after the swap completes, ensuring newly injected Alpine components initialize correctly.

tip

Use htmx:afterSettle rather than htmx:afterSwap as it fires after the DOM has fully stabilized.

Preventing Flash of Unstyled Content

Add this CSS to your stylesheet to hide elements until Alpine initializes:

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

Elements with x-cloak will be hidden until Alpine removes the attribute on initialization, preventing flickering of unprocessed templates.

Escaping Curly Braces

Alpine.js uses { } for JavaScript expressions. Since Luat also uses curly braces for template expressions, you must escape Alpine's braces:

<!-- Luat template -->
<div x-data="\{ count: 0 \}">

This outputs literal braces that Alpine can interpret:

<!-- Rendered HTML -->
<div x-data="{ count: 0 }">

Basic Patterns

Toggle Visibility

Use x-show to toggle element visibility and x-cloak to prevent flash:

main.luat
Loading Luat...

Counter with Animation

main.luat
Loading Luat...

Luat + Alpine Components

Accordion with Lua Data

Define FAQ items in a Lua script and iterate with {#each}. Alpine handles the open/close state client-side:

main.luat
Loading Luat...

Tabs with Dynamic Content

Define tabs in Lua, render with {#each}, and let Alpine manage the active state:

main.luat
Loading Luat...
main.luat
Loading Luat...

Reusable Component Patterns

Create a reusable Modal component that accepts a title and renders children as content:

Loading Luat...

Accordion Component

A reusable accordion item component used with Lua data:

Loading Luat...

Resources