Alpine.js is a lightweight JavaScript framework that lets you add interactivity directly in your HTML markup. No build step, no virtual DOM, no JSX — just HTML attributes.
Why Alpine.js?
- 14KB — tiny footprint, CDN-ready
-
No build step — works with a single
<script>tag -
HTML-first — logic lives in your markup with
x-directives - Perfect companion for HTMX, Tailwind, server-rendered HTML
Quick Start
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<div x-data="{ count: 0 }">
<p x-text="`Count: ${count}`"></p>
<button @click="count++">Increment</button>
</div>
That's it. No npm, no webpack, no config files.
Core Directives
<!-- x-data: reactive state -->
<div x-data="{ open: false, name: '' }">
<!-- x-show: toggle visibility -->
<div x-show="open">
<p>This is visible when open is true</p>
</div>
<!-- x-bind: dynamic attributes -->
<button :class="open ? 'bg-blue' : 'bg-gray'">Toggle</button>
<!-- x-on / @: event listeners -->
<button @click="open = !open">Toggle</button>
<!-- x-model: two-way binding -->
<input x-model="name" placeholder="Your name">
<p x-text="`Hello, ${name}`"></p>
<!-- x-if: conditional rendering -->
<template x-if="name.length > 0">
<p>Welcome!</p>
</template>
<!-- x-for: loops -->
<template x-for="item in ['a', 'b', 'c']">
<li x-text="item"></li>
</template>
</div>
Dropdown Component
<div x-data="{ open: false }" @click.outside="open = false">
<button @click="open = !open">
Menu
</button>
<div x-show="open" x-transition>
<a href="/profile">Profile</a>
<a href="/settings">Settings</a>
<a href="/logout">Logout</a>
</div>
</div>
Fetching Data
<div x-data="{ users: [], loading: true }"
x-init="fetch('/api/users')
.then(r => r.json())
.then(data => { users = data; loading = false })">
<p x-show="loading">Loading...</p>
<template x-for="user in users" :key="user.id">
<div>
<h3 x-text="user.name"></h3>
<p x-text="user.email"></p>
</div>
</template>
</div>
Alpine Store (Global State)
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('cart', {
items: [],
add(item) {
this.items.push(item);
},
get total() {
return this.items.reduce((sum, i) => sum + i.price, 0);
},
});
});
</script>
<div x-data>
<p>Cart: <span x-text="$store.cart.items.length"></span> items</p>
<p>Total: $<span x-text="$store.cart.total"></span></p>
<button @click="$store.cart.add({ name: 'Widget', price: 9.99 })">
Add Widget
</button>
</div>
Plugins
<!-- Mask plugin -->
<script src="https://cdn.jsdelivr.net/npm/@alpinejs/mask@3.x.x/dist/cdn.min.js"></script>
<input x-mask="(999) 999-9999" placeholder="Phone">
<input x-mask="99/99/9999" placeholder="Date">
<!-- Intersect plugin -->
<script src="https://cdn.jsdelivr.net/npm/@alpinejs/intersect@3.x.x/dist/cdn.min.js"></script>
<div x-intersect="visible = true">Lazy load this</div>
<!-- Persist plugin -->
<script src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
<div x-data="{ theme: $persist('dark') }">
<button @click="theme = theme === 'dark' ? 'light' : 'dark'">Toggle</button>
</div>
Need dynamic data for your Alpine.js app? Check out my Apify actors for web scraping APIs, or email spinov001@gmail.com for custom solutions.
Alpine.js, Petite Vue, or vanilla JS — what do you use for lightweight interactivity? Share below!
Top comments (0)