Alpine.js Integration
While Nex advocates for “Server-Driven” and “Minimalist Interaction,” Alpine.js is Nex’s perfect partner when handling pure client-side logic (like animations, transitions, and complex modal states).
1. Why Use Alpine.js?
Nex handles server communication, while Alpine.js handles local UI logic.
- Lightweight: No Virtual DOM, operates directly on existing HTML.
-
Declarative: Write logic directly in HTML via
x-data,x-show, andx-onattributes. - Zero Build: Included directly via CDN, aligning with Nex’s development philosophy.
2. Integration Method
Include it in the <head> tag of your src/layouts.ex (using defer is recommended):
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
3. Core Application Patterns
A. Global State and Persistence
You can define global state on the <body> tag and use localStorage for persistence (e.g., theme switching).
# src/layouts.ex
<body
x-data="{ theme: localStorage.getItem('theme') || 'light' }"
x-init="$watch('theme', val => localStorage.setItem('theme', val))"
x-bind:data-theme="theme"
>
<button @click="theme = theme === 'light' ? 'dark' : 'light'">Toggle Theme</button>
{raw(@inner_content)}
</body>
B. Local UI State (Tabs & Modals)
Use Alpine to handle UI toggles that don’t require server involvement, ensuring instant responsiveness.
~H"""
<div x-data="{ currentTab: 'users', modalOpen: false }">
<!-- Tab Switching -->
<div class="tabs">
<a :class="{ 'active': currentTab === 'users' }" @click="currentTab = 'users'">User List</a>
<a :class="{ 'active': currentTab === 'settings' }" @click="currentTab = 'settings'">Settings</a>
</div>
<div x-show="currentTab === 'users'">
<button @click="modalOpen = true; $nextTick(() => $refs.nameInput.focus())">Add User</button>
</div>
<!-- Modal -->
<div x-show="modalOpen" class="modal">
<div @click.away="modalOpen = false">
<input x-ref="nameInput" placeholder="Enter name...">
<button @click="modalOpen = false">Close</button>
</div>
</div>
</div>
"""
C. Global Notifications (Toasts)
Leverage Alpine’s event system for cross-component notifications.
# Toast container in Layout
<div x-data="{ show: false, message: '' }"
x-on:show-toast.window="show = true; message = $event.detail; setTimeout(() => show = false, 3000)"
x-show="show">
<span x-text="message"></span>
</div>
# Triggering a notification
<button @click="$dispatch('show-toast', 'Action successful!')">Click Me</button>
4. Working with Nex Actions
Resetting State After Request
You can listen to HTMX lifecycle events to reset Alpine state.
~H"""
<div x-data="{ comment: '' }">
<form hx-post="/add_comment" @htmx:after-request="comment = ''">
<textarea x-model="comment" name="text"></textarea>
<button type="submit">Send</button>
</form>
</div>
"""
Partial Refresh & State Persistence
When a Nex Action returns an HTML fragment and updates the DOM, if the parent element has x-data, Alpine automatically re-initializes newly inserted elements.
6. Complete Example Project
To dive deeper into integrating Alpine.js with Nex, please refer to our official showcase project:
Features included in this example:
-
Theme Switching: Managed via Alpine in
layouts.exwithlocalStoragepersistence. - Client-side Tabs: Rapid view switching without server involvement.
-
Responsive Modals: Includes auto-focusing (
$refs) and click-outside-to-close interactions. - Global Toast System: Cross-component notifications using Alpine’s event system.
- Complex Form Interaction: Combines Nex Action’s asynchronous submission with instant frontend state resetting.