Overview
Alpine.js is the client-side reactive framework used in the Prismatic Platform for interactive UI behaviors that complement Phoenix LiveView's server-rendered approach. At just ~15KB minified and gzipped, Alpine provides reactive data binding, event handling, and DOM manipulation through declarative HTML attributes -- no build step, no bundler, no compilation pipeline required. It occupies the sweet spot between plain JavaScript and a full single-page application framework, delivering reactivity without the complexity overhead.
The Prismatic Platform uses Alpine.js for client-side-only interactions: search filtering, tab switching, dropdown menus, modal dialogs, and data table sorting. These interactions benefit from instant client-side response without a server round-trip, improving perceived performance for common UI operations. On the promotional site built with Zola, Alpine handles the entire registry filtering system, allowing visitors to search and filter through 404 agents, 211 commands, and 45 technologies entirely in the browser without any backend calls.
Alpine's x-data, x-bind, and x-on directives integrate cleanly with Zola's static site generation for the platform's documentation and promotional sites, while also working alongside LiveView in the main application. This dual-context usage means Alpine components are authored once and deployed across both static and dynamic contexts without modification, reducing the maintenance burden of shared UI patterns.
Key Features
Alpine.js provides a focused set of reactive primitives that cover the vast majority of client-side interaction needs without introducing framework-level complexity.
- Declarative Syntax:
x-data,x-show,x-bind,x-ondirectives embedded directly in HTML markup for transparent behavior - Reactive Data: Automatic DOM updates when underlying data properties change, with no manual re-rendering or virtual DOM diffing
- Transitions: Built-in CSS transition support with
x-transitionfor smooth show/hide animations with configurable enter/leave states - No Build Step: Script tag inclusion only -- no webpack, no bundler, no compilation required, keeping the toolchain minimal
- Small Footprint: ~15KB minified and gzipped, contributing minimal overhead to page load times and Core Web Vitals scores
- Component Pattern: Reusable behaviors registered via
Alpine.data()for structured, testable components with encapsulated state - LiveView Compatibility: Works alongside Phoenix LiveView without DOM conflicts through careful scope isolation
- Store Pattern: Global state management via
Alpine.store()for cross-component data sharing without prop drilling - Magic Properties:
$refs,$el,$dispatch, and$nextTickfor common DOM interaction patterns
| Directive | Purpose | Platform Usage |
|---|---|---|
x-data | Component initialization | Every interactive widget |
x-model | Two-way data binding | Search inputs, form fields |
x-show | Conditional visibility | Dropdown menus, collapsible panels |
x-for | List rendering | Registry tables, card grids |
x-on / @ | Event handling | Button clicks, keyboard shortcuts |
x-bind / : | Dynamic attributes | CSS classes, disabled states |
x-transition | Enter/leave animations | Modal dialogs, toast notifications |
x-init | Lifecycle hook | Data fetching on mount |
Platform Integration
Alpine.js powers client-side filtering and interactions across all list views on the promo site, including the agent registry, command catalog, and technology explorer pages. The implementation demonstrates how Alpine handles complex filtering logic entirely in the browser.
<!-- Alpine.js powered search and filter for technology registry -->
<div x-data="techTable()">
<input type="text"
x-model="searchQuery"
@input="filterTech()"
class="bg-gray-800 text-white border-gray-700 rounded-lg px-4 py-2 w-full"
placeholder="Search technologies...">
<div class="flex gap-2 mt-3">
<template x-for="cat in categories" :key="cat">
<button @click="toggleCategory(cat)"
:class="activeCategory === cat ? 'bg-blue-600' : 'bg-gray-700'"
class="px-3 py-1 rounded-full text-sm text-white"
x-text="cat"></button>
</template>
</div>
<template x-for="tech in paginatedTech" :key="tech.id">
<div class="p-4 rounded-xl border border-gray-700 hover:border-blue-500 transition"
@click="window.location.href = tech.url">
<h3 x-text="tech.name" class="text-white font-semibold"></h3>
<p x-text="tech.description" class="text-gray-400 text-sm mt-1"></p>
</div>
</template>
<div class="mt-4 text-gray-400 text-sm">
Showing <span x-text="paginatedTech.length"></span>
of <span x-text="filteredTech.length"></span> technologies
</div>
</div>
In the main prismatic_web application, Alpine handles lightweight interactions that do not require server state -- collapsible sidebar sections, clipboard copy buttons, and local preference toggles such as table density settings. These are interactions where a server round-trip would add unnecessary latency.
Architecture
Alpine.js occupies a specific niche in the platform's frontend architecture. The platform follows a layered approach where each tool handles the interaction tier it is best suited for.
| Interaction Tier | Handler | Latency | State Location |
|---|---|---|---|
| Page navigation | Phoenix Router | Full page load | Server |
| Dynamic content | Phoenix LiveView | ~50ms WebSocket | Server (assigns) |
| Client-only UI | Alpine.js | Instant (0ms) | Browser (x-data) |
| Styling/layout | TailwindCSS | N/A (CSS) | Stylesheet |
This separation ensures that Alpine never competes with LiveView for DOM control. Alpine manages visibility, class toggling, and local state, while LiveView manages server-rendered content. The boundary is explicit: if an interaction requires data from the database or business logic, it belongs to LiveView. If it is purely presentational (show/hide, filter client-side data, toggle theme), it belongs to Alpine.
// Alpine component registration pattern used across the platform
document.addEventListener('alpine:init', () => {
Alpine.data'registryTable', () =>{
: '',
: 'all',
:,
get filteredItems() {
return this.items.filteritem => {
const matchesSearch = item.name.toLowerCase
.includesthis.searchQuery.toLowerCase;
const matchesCategory = this.activeCategory === 'all'
|| item.category === this.activeCategory;
return matchesSearch && matchesCategory;
};
},
get categories() {
return'all', ...new Setthis.items.mapi => i.category;
}
};
});Performance Characteristics
Alpine.js was selected specifically for its minimal performance impact. The platform's page load performance standard requires all pages to load under 250ms, and Alpine's ~15KB footprint ensures it does not contribute meaningfully to that budget.
| Metric | Value | Impact |
|---|---|---|
| Bundle size (min+gzip) | ~15KB | Negligible on page load |
| Parse time | < 5ms | Below measurement threshold |
| First interaction | Instant | No hydration delay |
| Memory per component | ~1-2KB | Minimal overhead per widget |
| DOM update granularity | Targeted | No virtual DOM diffing overhead |
| LiveView compatibility | Full | No DOM patching conflicts |
Compared to React (~45KB) or Vue (~33KB), Alpine's footprint is 2-3x smaller with no build step overhead. For the promo site, where pages are static HTML enhanced with filtering, Alpine provides the optimal performance-to-capability ratio.
Configuration
Alpine.js is loaded via CDN with the defer attribute to avoid blocking page rendering. No additional build tooling is required, keeping the deployment pipeline simple and fast.
<!-- Alpine.js loaded via CDN with defer in base.html -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.5/dist/cdn.min.js"></script>
For the promo site, Alpine component definitions are placed in inline <script> blocks within Zola templates, keeping behavior co-located with the markup it controls. In the main application, Alpine components are defined in the Phoenix asset pipeline alongside LiveView hooks.
Platform-wide Alpine configuration ensures consistent behavior across all pages:
// Global Alpine configuration
document.addEventListener('alpine:init', () => {
// Global store for shared state
Alpine.store'preferences', {
: 'comfortable',
: false,
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
localStorage.setItem'sidebar', this.sidebarCollapsed;
}
};
});Best Practices
The platform follows several conventions for Alpine.js usage to maintain consistency and prevent conflicts with LiveView. These practices are enforced through code review and template validation scripts.
- Use
x-datafor local state only -- server-managed state belongs in LiveView assigns, not Alpine data properties - Prefer
Alpine.data()registration over inlinex-dataobjects for any component exceeding five lines of logic - Avoid DOM manipulation that conflicts with LiveView's DOM patching -- Alpine controls visibility and class toggling, LiveView controls content
- Keep Alpine logic minimal -- if an interaction requires server data, it should be a LiveView event, not an Alpine fetch call
- Use
$dispatchfor cross-component communication rather than direct DOM manipulation or global variables - Leverage
x-transitionfor smooth animations instead of custom CSS transitions to maintain consistency - Test with data attributes -- use
data-testidattributes on Alpine-controlled elements for reliable end-to-end testing - Prefer computed getters (
get filteredItems()) over manual filtering in event handlers for automatic reactivity
Comparison
Alpine.js was chosen over heavier frontend frameworks because the Prismatic Platform's architecture delegates most interactivity to Phoenix LiveView on the server side. Alpine fills the remaining gap for client-only interactions without introducing build complexity.
| Criterion | Alpine.js | React | Vue | Stimulus |
|---|---|---|---|---|
| Bundle size | ~15KB | ~45KB | ~33KB | ~8KB |
| Build step required | No | Yes | Yes | No |
| Learning curve | Minimal | Significant | Moderate | Minimal |
| LiveView compatibility | Excellent | Poor (DOM conflicts) | Fair | Good |
| Reactivity model | Proxy-based | Virtual DOM | Proxy-based | None (DOM-only) |
| Component registration | Alpine.data() | JSX components | SFC / Options | Controllers |
| State management | Alpine.store() | Context / Redux | Pinia / Vuex | N/A |
Related Technologies
- Phoenix LiveView - Server-side real-time UI, complementary to Alpine's client-side role
- TailwindCSS - Utility-first CSS framework styled alongside Alpine directives
- Flowbite - Component library that uses Alpine-compatible JavaScript patterns
- Zola - Static site generator where Alpine provides all interactivity for the promo site
- Phoenix Framework - Web framework providing the server-side application layer
Related Apps
- prismatic_web - Client-side Alpine interactions in LiveView dashboards for collapsible panels and local preferences
- prismatic_perimeter - Alpine-powered filter controls on the EASM dashboard for asset filtering
- prismatic_api - Swagger UI enhanced with Alpine.js for interactive API exploration
Connect & Contribute
Created by Tomáš Korcak (korczis) | Open Source under GHL