🎯 Recommended Samples
Balanced sample collections from various categories for you to explore
Alpine.js Samples
Alpine.js framework examples including reactive components, data binding, and modern Alpine.js patterns
💻 Alpine.js Hello World html
🟢 simple
⭐
Basic Alpine.js setup and Hello World examples with reactive data
⏱️ 15 min
🏷️ alpinejs, reactive, components
Prerequisites:
Basic HTML knowledge, JavaScript basics
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpine.js Hello World</title>
<!-- Alpine.js -->
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: white;
}
.btn {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
.btn:hover {
background: #0056b3;
}
.highlight {
background: #fff3cd;
padding: 1rem;
border-radius: 4px;
border-left: 4px solid #ffc107;
}
</style>
</head>
<body>
<h1>Alpine.js Hello World</h1>
<!-- Basic Reactive Data -->
<div class="card" x-data="{ message: 'Hello, Alpine.js!' }">
<h2>Basic Reactive Data</h2>
<p x-text="message"></p>
<button @click="message = 'Button clicked!'" class="btn">
Change Message
</button>
</div>
<!-- Counter Example -->
<div class="card" x-data="{ count: 0 }">
<h2>Counter Component</h2>
<p>Current count: <strong x-text="count"></strong></p>
<button @click="count--" class="btn">Decrement</button>
<button @click="count++" class="btn">Increment</button>
<button @click="count = 0" class="btn">Reset</button>
</div>
<!-- Input Binding -->
<div class="card" x-data="{ name: '' }">
<h2>Input Binding</h2>
<input
type="text"
x-model="name"
placeholder="Enter your name"
style="padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%; margin-bottom: 1rem;">
<p x-show="name">
Hello, <strong x-text="name"></strong>!
</p>
<p x-show="!name" style="color: #6c757d;">
Please enter your name above.
</p>
</div>
<!-- Toggle Visibility -->
<div class="card" x-data="{ showDetails: false }">
<h2>Toggle Visibility</h2>
<button @click="showDetails = !showDetails" class="btn">
<span x-text="showDetails ? 'Hide' : 'Show'"></span> Details
</button>
<div x-show="showDetails" x-transition class="highlight" style="margin-top: 1rem;">
<h3>Hidden Content</h3>
<p>This content is toggled using Alpine.js x-show and x-transition directives.</p>
<p>The transition effect is automatically applied when the element appears or disappears.</p>
</div>
</div>
<!-- Conditional Classes -->
<div class="card" x-data="{
isDark: false,
styles: {
light: { background: '#f8f9fa', color: '#212529', padding: '1rem', borderRadius: '4px' },
dark: { background: '#212529', color: '#f8f9fa', padding: '1rem', borderRadius: '4px' }
}
}">
<h2>Conditional Styling</h2>
<button @click="isDark = !isDark" class="btn">
Toggle Theme
</button>
<div
x-bind="isDark ? styles.dark : styles.light"
style="margin-top: 1rem;">
<p x-text="isDark ? 'Dark mode enabled' : 'Light mode enabled'"></p>
<p>Current theme: <strong x-text="isDark ? 'Dark' : 'Light'"></strong></p>
</div>
</div>
<!-- Array Iteration -->
<div class="card" x-data="{
items: [
{ id: 1, name: 'Item 1', completed: false },
{ id: 2, name: 'Item 2', completed: true },
{ id: 3, name: 'Item 3', completed: false }
],
newItem: ''
}">
<h2>Todo List</h2>
<!-- Add new item -->
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem;">
<input
type="text"
x-model="newItem"
@keyup.enter="if(newItem.trim()) { items.push({ id: Date.now(), name: newItem, completed: false }); newItem = '' }"
placeholder="Add new item"
style="flex: 1; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
<button @click="if(newItem.trim()) { items.push({ id: Date.now(), name: newItem, completed: false }); newItem = '' }" class="btn">
Add
</button>
</div>
<!-- Items list -->
<ul style="list-style: none; padding: 0;">
<template x-for="item in items" :key="item.id">
<li style="display: flex; align-items: center; padding: 0.5rem; border-bottom: 1px solid #eee;">
<input
type="checkbox"
x-model="item.completed"
style="margin-right: 0.5rem;">
<span
x-text="item.name"
:style="{ textDecoration: item.completed ? 'line-through' : 'none' }"
style="flex: 1;"></span>
<button
@click="items = items.filter(i => i.id !== item.id)"
style="background: #dc3545; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; cursor: pointer;">
Delete
</button>
</li>
</template>
</ul>
<!-- Stats -->
<div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
<p>Total items: <strong x-text="items.length"></strong></p>
<p>Completed: <strong x-text="items.filter(i => i.completed).length"></strong></p>
<p>Pending: <strong x-text="items.filter(i => !i.completed).length"></strong></p>
</div>
</div>
<!-- Form Example -->
<div class="card" x-data="{
form: {
name: '',
email: '',
message: ''
},
submitted: false,
errors: {}
}">
<h2>Contact Form</h2>
<form @submit.prevent="
errors = {};
if(!form.name) errors.name = 'Name is required';
if(!form.email) errors.email = 'Email is required';
if(!form.message) errors.message = 'Message is required';
if(Object.keys(errors).length === 0) {
submitted = true;
setTimeout(() => submitted = false, 3000);
}
">
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Name:</label>
<input
type="text"
x-model="form.name"
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
<p x-show="errors.name" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.name"></p>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Email:</label>
<input
type="email"
x-model="form.email"
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
<p x-show="errors.email" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.email"></p>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">Message:</label>
<textarea
x-model="form.message"
rows="4"
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;"></textarea>
<p x-show="errors.message" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.message"></p>
</div>
<button type="submit" class="btn">Submit</button>
</form>
<div x-show="submitted" x-transition class="highlight" style="margin-top: 1rem;">
<strong>Form submitted successfully!</strong>
<p>Name: <span x-text="form.name"></span></p>
<p>Email: <span x-text="form.email"></span></p>
<p>Message: <span x-text="form.message"></span></p>
</div>
</div>
<!-- Clock Example -->
<div class="card" x-data="{
time: new Date().toLocaleTimeString(),
init() {
setInterval(() => {
this.time = new Date().toLocaleTimeString();
}, 1000);
}
}">
<h2>Live Clock</h2>
<div style="font-size: 2rem; font-weight: bold; text-align: center; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
<span x-text="time"></span>
</div>
<p style="text-align: center; color: #6c757d; margin-top: 0.5rem;">
Updates every second using setInterval in the init() lifecycle hook.
</p>
</div>
<!-- Shopping Cart -->
<div class="card" x-data="{
products: [
{ id: 1, name: 'Laptop', price: 999, quantity: 0 },
{ id: 2, name: 'Mouse', price: 29, quantity: 0 },
{ id: 3, name: 'Keyboard', price: 79, quantity: 0 }
],
get total() {
return this.products.reduce((sum, product) => sum + (product.price * product.quantity), 0);
},
get itemCount() {
return this.products.reduce((sum, product) => sum + product.quantity, 0);
}
}">
<h2>Shopping Cart</h2>
<template x-for="product in products" :key="product.id">
<div style="display: flex; justify-content: space-between; align-items: center; padding: 1rem; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 0.5rem;">
<div>
<strong x-text="product.name"></strong>
<span style="color: #6c757d;">($<span x-text="product.price"></span>)</span>
</div>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<button
@click="product.quantity = Math.max(0, product.quantity - 1)"
style="background: #6c757d; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; cursor: pointer;">
-
</button>
<span x-text="product.quantity" style="min-width: 2rem; text-align: center;"></span>
<button
@click="product.quantity++"
style="background: #28a745; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; cursor: pointer;">
+
</button>
</div>
</div>
</template>
<div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; text-align: right;">
<p>Items: <strong x-text="itemCount"></strong></p>
<p>Total: <strong>$<span x-text="total.toFixed(2)"></span></strong></p>
<button
@click="products.forEach(p => p.quantity = 0)"
style="background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; margin-top: 0.5rem;">
Clear Cart
</button>
</div>
</div>
</body>
</html>
💻 Alpine.js Component Patterns html
🟡 intermediate
⭐⭐⭐
Advanced component patterns, data flow, and architecture with Alpine.js
⏱️ 30 min
🏷️ alpinejs, components, patterns
Prerequisites:
Alpine.js basics, JavaScript ES6+, Component concepts
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpine.js Component Patterns</title>
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: white;
}
.tabs {
display: flex;
border-bottom: 1px solid #ddd;
}
.tab {
padding: 0.75rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.tab.active {
border-bottom-color: #007bff;
color: #007bff;
font-weight: bold;
}
.tab-content {
padding: 1rem 0;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 500px;
width: 90%;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
min-width: 200px;
z-index: 100;
}
.dropdown-item {
padding: 0.5rem 1rem;
cursor: pointer;
}
.dropdown-item:hover {
background: #f8f9fa;
}
.accordion-item {
border: 1px solid #ddd;
margin-bottom: 0.5rem;
border-radius: 4px;
}
.accordion-header {
padding: 1rem;
background: #f8f9fa;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.accordion-content {
padding: 1rem;
border-top: 1px solid #ddd;
}
.slide-item {
min-width: 100%;
padding: 2rem;
background: #f8f9fa;
text-align: center;
border-radius: 8px;
}
.carousel-container {
overflow: hidden;
border-radius: 8px;
}
.carousel-track {
display: flex;
transition: transform 0.3s ease;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 1rem 1.5rem;
border-radius: 4px;
color: white;
font-weight: bold;
z-index: 1000;
}
.notification.success { background: #28a745; }
.notification.error { background: #dc3545; }
.notification.warning { background: #ffc107; color: #212529; }
.notification.info { background: #17a2b8; }
</style>
</head>
<body>
<h1>Alpine.js Component Patterns</h1>
<!-- Tabs Component -->
<div class="card" x-data="tabsComponent()">
<h2>Tabs Component</h2>
<div class="tabs">
<template x-for="tab in tabs" :key="tab.id">
<div
@click="activeTab = tab.id"
:class="{ 'active': activeTab === tab.id }"
class="tab"
x-text="tab.title">
</div>
</template>
</div>
<div class="tab-content">
<template x-for="tab in tabs" :key="tab.id">
<div x-show="activeTab === tab.id" x-transition>
<h3 x-text="tab.title"></h3>
<p x-text="tab.content"></p>
</div>
</template>
</div>
</div>
<!-- Modal Component -->
<div class="card" x-data="modalComponent()">
<h2>Modal Component</h2>
<button @click="open('basic')" class="btn" style="margin-right: 0.5rem;">Open Basic Modal</button>
<button @click="open('confirm')" class="btn" style="margin-right: 0.5rem;">Open Confirm Modal</button>
<button @click="open('form')" class="btn">Open Form Modal</button>
<!-- Basic Modal -->
<div x-show="currentModal === 'basic'" x-transition class="modal">
<div class="modal-content" @click.away="close()">
<h3>Basic Modal</h3>
<p>This is a basic modal dialog with some content.</p>
<button @click="close()" class="btn">Close</button>
</div>
</div>
<!-- Confirm Modal -->
<div x-show="currentModal === 'confirm'" x-transition class="modal">
<div class="modal-content" @click.away="close()">
<h3>Confirm Action</h3>
<p>Are you sure you want to perform this action?</p>
<div style="margin-top: 1rem;">
<button @click="confirm()" class="btn" style="margin-right: 0.5rem;">Confirm</button>
<button @click="close()">Cancel</button>
</div>
</div>
</div>
<!-- Form Modal -->
<div x-show="currentModal === 'form'" x-transition class="modal">
<div class="modal-content" @click.away="close()">
<h3>User Information</h3>
<form @submit.prevent="submitForm()">
<div style="margin-bottom: 1rem;">
<label>Name:</label>
<input type="text" x-model="formData.name" required style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 1rem;">
<label>Email:</label>
<input type="email" x-model="formData.email" required style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
</div>
<button type="submit" class="btn" style="margin-right: 0.5rem;">Submit</button>
<button type="button" @click="close()">Cancel</button>
</form>
</div>
</div>
</div>
<!-- Dropdown Component -->
<div class="card" x-data="dropdownComponent()">
<h2>Dropdown Component</h2>
<div class="dropdown">
<button @click="toggle()" class="btn">
Select Option <span x-text="selected ? '✓' : '▼'"></span>
</button>
<div x-show="isOpen" x-transition @click.away="close()" class="dropdown-menu">
<template x-for="option in options" :key="option.value">
<div
@click="select(option)"
class="dropdown-item"
:style="{ fontWeight: selected === option.value ? 'bold' : 'normal' }">
<span x-text="option.icon" style="margin-right: 0.5rem;"></span>
<span x-text="option.label"></span>
</div>
</template>
</div>
</div>
<p style="margin-top: 1rem;">Selected: <strong x-text="selectedLabel"></strong></p>
</div>
<!-- Accordion Component -->
<div class="card" x-data="accordionComponent()">
<h2>Accordion Component</h2>
<template x-for="(item, index) in items" :key="index">
<div class="accordion-item">
<div class="accordion-header" @click="toggle(index)">
<span x-text="item.title"></span>
<span x-text="openItems.includes(index) ? '▼' : '▶'"></span>
</div>
<div x-show="openItems.includes(index)" x-collapse>
<div class="accordion-content">
<p x-text="item.content"></p>
</div>
</div>
</div>
</template>
</div>
<!-- Carousel Component -->
<div class="card" x-data="carouselComponent()">
<h2>Carousel Component</h2>
<div class="carousel-container">
<div class="carousel-track" :style="{ transform: `translateX(-${currentIndex * 100}%)` }">
<template x-for="(slide, index) in slides" :key="index">
<div class="slide-item">
<h3 x-text="slide.title"></h3>
<p x-text="slide.content"></p>
</div>
</template>
</div>
</div>
<div style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;">
<button @click="prev()" class="btn" :disabled="currentIndex === 0">Previous</button>
<div>
<template x-for="(slide, index) in slides" :key="index">
<button
@click="goTo(index)"
:style="{ background: currentIndex === index ? '#007bff' : '#6c757d' }"
style="width: 10px; height: 10px; border-radius: 50%; margin: 0 2px; border: none; cursor: pointer;">
</button>
</template>
</div>
<button @click="next()" class="btn" :disabled="currentIndex === slides.length - 1">Next</button>
</div>
</div>
<!-- Search Filter Component -->
<div class="card" x-data="searchComponent()">
<h2>Search Filter Component</h2>
<div style="margin-bottom: 1rem;">
<input
type="text"
x-model="searchTerm"
placeholder="Search users..."
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 1rem;">
<label>
<input type="checkbox" x-model="showActive" style="margin-right: 0.5rem;">
Show only active users
</label>
</div>
<div style="margin-bottom: 1rem;">
<label>Role filter:</label>
<select x-model="selectedRole" style="margin-left: 0.5rem; padding: 0.25rem; border: 1px solid #ddd; border-radius: 4px;">
<option value="">All</option>
<option value="admin">Admin</option>
<option value="user">User</option>
<option value="guest">Guest</option>
</select>
</div>
<div>
<p><strong>Filtered results:</strong></p>
<template x-for="user in filteredUsers" :key="user.id">
<div style="padding: 0.5rem; border: 1px solid #eee; margin-bottom: 0.25rem; border-radius: 4px;">
<span x-text="user.name"></span>
<span style="margin-left: 0.5rem; color: #6c757d;" x-text="'(' + user.role + ')'"></span>
<span
:style="{
color: user.active ? '#28a745' : '#dc3545',
marginLeft: '0.5rem'
}"
x-text="user.active ? '✓ Active' : '✗ Inactive'">
</span>
</div>
</template>
<p x-show="filteredUsers.length === 0" style="color: #6c757d; font-style: italic;">
No users found matching the criteria.
</p>
</div>
</div>
<!-- Form Validation Component -->
<div class="card" x-data="formValidationComponent()">
<h2>Form Validation Component</h2>
<form @submit.prevent="validateAndSubmit()">
<div style="margin-bottom: 1rem;">
<label>Username (min 3 chars):</label>
<input
type="text"
x-model="form.username"
@blur="validateField('username')"
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;"
:style="{ borderColor: errors.username ? '#dc3545' : touched.username && !errors.username ? '#28a745' : '#ddd' }">
<p x-show="errors.username" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.username"></p>
</div>
<div style="margin-bottom: 1rem;">
<label>Email:</label>
<input
type="email"
x-model="form.email"
@blur="validateField('email')"
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;"
:style="{ borderColor: errors.email ? '#dc3545' : touched.email && !errors.email ? '#28a745' : '#ddd' }">
<p x-show="errors.email" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.email"></p>
</div>
<div style="margin-bottom: 1rem;">
<label>Password (min 8 chars):</label>
<input
type="password"
x-model="form.password"
@blur="validateField('password')"
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;"
:style="{ borderColor: errors.password ? '#dc3545' : touched.password && !errors.password ? '#28a745' : '#ddd' }">
<p x-show="errors.password" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.password"></p>
</div>
<div style="margin-bottom: 1rem;">
<label>Confirm Password:</label>
<input
type="password"
x-model="form.confirmPassword"
@blur="validateField('confirmPassword')"
style="width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;"
:style="{ borderColor: errors.confirmPassword ? '#dc3545' : touched.confirmPassword && !errors.confirmPassword ? '#28a745' : '#ddd' }">
<p x-show="errors.confirmPassword" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.confirmPassword"></p>
</div>
<div style="margin-bottom: 1rem;">
<label>
<input type="checkbox" x-model="form.terms" style="margin-right: 0.5rem;">
I accept the terms and conditions
</label>
<p x-show="errors.terms" style="color: #dc3545; font-size: 0.875rem;" x-text="errors.terms"></p>
</div>
<button type="submit" class="btn" :disabled="!isValid">Register</button>
</form>
<div x-show="submitted" x-transition style="margin-top: 1rem; padding: 1rem; background: #d4edda; border-radius: 4px;">
<strong>Form submitted successfully!</strong>
</div>
</div>
<!-- Notification System -->
<div class="card" x-data="notificationSystem()">
<h2>Notification System</h2>
<div>
<button @click="show('success', 'Operation completed successfully!')" class="btn" style="margin-right: 0.5rem; background: #28a745;">Success</button>
<button @click="show('error', 'Something went wrong!')" class="btn" style="margin-right: 0.5rem; background: #dc3545;">Error</button>
<button @click="show('warning', 'Please be careful!')" class="btn" style="margin-right: 0.5rem; background: #ffc107; color: #212529;">Warning</button>
<button @click="show('info', 'Here is some information')" class="btn" style="background: #17a2b8;">Info</button>
</div>
</div>
<script>
// Component initialization functions
function tabsComponent() {
return {
activeTab: 'tab1',
tabs: [
{ id: 'tab1', title: 'Home', content: 'Welcome to the home tab. This is where you can find general information and updates.' },
{ id: 'tab2', title: 'Profile', content: 'Manage your profile information, settings, and preferences here.' },
{ id: 'tab3', title: 'Settings', content: 'Configure your application settings and customize your experience.' }
]
}
}
function modalComponent() {
return {
currentModal: null,
formData: { name: '', email: '' },
open(type) {
this.currentModal = type;
},
close() {
this.currentModal = null;
},
confirm() {
alert('Action confirmed!');
this.close();
},
submitForm() {
alert('Form submitted: ' + JSON.stringify(this.formData));
this.close();
}
}
}
function dropdownComponent() {
return {
isOpen: false,
selected: null,
options: [
{ value: 'edit', label: 'Edit', icon: '✏️' },
{ value: 'copy', label: 'Copy', icon: '📋' },
{ value: 'delete', label: 'Delete', icon: '🗑️' },
{ value: 'share', label: 'Share', icon: '🔗' }
],
toggle() {
this.isOpen = !this.isOpen;
},
close() {
this.isOpen = false;
},
select(option) {
this.selected = option.value;
this.close();
},
get selectedLabel() {
const option = this.options.find(o => o.value === this.selected);
return option ? option.label : 'None selected';
}
}
}
function accordionComponent() {
return {
openItems: [0],
items: [
{ title: 'What is Alpine.js?', content: 'Alpine.js is a rugged, minimal JavaScript framework for adding client-side interactivity to your server-rendered HTML.' },
{ title: 'Why use Alpine.js?', content: 'Alpine.js provides the reactive and declarative nature of big frameworks like Vue or React at a much lower cost and with much less complexity.' },
{ title: 'How does it work?', content: 'You include Alpine.js on your page and use x-data, x-bind, x-on, and other directives to add interactivity to your HTML.' }
],
toggle(index) {
const position = this.openItems.indexOf(index);
if (position === -1) {
this.openItems.push(index);
} else {
this.openItems.splice(position, 1);
}
}
}
}
function carouselComponent() {
return {
currentIndex: 0,
slides: [
{ title: 'Slide 1', content: 'This is the first slide with some example content.' },
{ title: 'Slide 2', content: 'This is the second slide with different content.' },
{ title: 'Slide 3', content: 'This is the third slide with yet more content.' }
],
next() {
if (this.currentIndex < this.slides.length - 1) {
this.currentIndex++;
}
},
prev() {
if (this.currentIndex > 0) {
this.currentIndex--;
}
},
goTo(index) {
this.currentIndex = index;
}
}
}
function searchComponent() {
return {
searchTerm: '',
showActive: false,
selectedRole: '',
users: [
{ id: 1, name: 'John Doe', role: 'admin', active: true },
{ id: 2, name: 'Jane Smith', role: 'user', active: true },
{ id: 3, name: 'Bob Johnson', role: 'guest', active: false },
{ id: 4, name: 'Alice Brown', role: 'user', active: true },
{ id: 5, name: 'Charlie Wilson', role: 'admin', active: false }
],
get filteredUsers() {
return this.users.filter(user => {
const matchesSearch = user.name.toLowerCase().includes(this.searchTerm.toLowerCase());
const matchesActive = !this.showActive || user.active;
const matchesRole = !this.selectedRole || user.role === this.selectedRole;
return matchesSearch && matchesActive && matchesRole;
});
}
}
}
function formValidationComponent() {
return {
form: {
username: '',
email: '',
password: '',
confirmPassword: '',
terms: false
},
errors: {},
touched: {},
submitted: false,
validateField(field) {
this.touched[field] = true;
switch(field) {
case 'username':
if (!this.form.username || this.form.username.length < 3) {
this.errors[field] = 'Username must be at least 3 characters';
} else {
delete this.errors[field];
}
break;
case 'email':
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
if (!this.form.email || !emailRegex.test(this.form.email)) {
this.errors[field] = 'Please enter a valid email';
} else {
delete this.errors[field];
}
break;
case 'password':
if (!this.form.password || this.form.password.length < 8) {
this.errors[field] = 'Password must be at least 8 characters';
} else {
delete this.errors[field];
}
break;
case 'confirmPassword':
if (this.form.password !== this.form.confirmPassword) {
this.errors[field] = 'Passwords do not match';
} else {
delete this.errors[field];
}
break;
case 'terms':
if (!this.form.terms) {
this.errors[field] = 'You must accept the terms';
} else {
delete this.errors[field];
}
break;
}
},
get isValid() {
return Object.keys(this.errors).length === 0 &&
this.form.username &&
this.form.email &&
this.form.password &&
this.form.confirmPassword &&
this.form.terms;
},
validateAndSubmit() {
// Validate all fields
['username', 'email', 'password', 'confirmPassword', 'terms'].forEach(field => {
this.validateField(field);
});
if (this.isValid) {
this.submitted = true;
setTimeout(() => this.submitted = false, 3000);
}
}
}
}
function notificationSystem() {
return {
show(type, message) {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.zIndex = '1000';
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
}
}
</script>
</body>
</html>
💻 Alpine.js State Management html
🟡 intermediate
⭐⭐⭐⭐
Advanced state management, stores, and data flow patterns with Alpine.js
⏱️ 35 min
🏷️ alpinejs, state, stores
Prerequisites:
Alpine.js basics, State management concepts, JavaScript ES6+
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpine.js State Management</title>
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
}
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
background: white;
}
.btn {
background: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
.btn:hover {
background: #0056b3;
}
.btn.success {
background: #28a745;
}
.btn.warning {
background: #ffc107;
color: #212529;
}
.btn.danger {
background: #dc3545;
}
.store-monitor {
background: #f8f9fa;
padding: 1rem;
border-radius: 4px;
font-family: monospace;
font-size: 0.875rem;
margin: 1rem 0;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border: 1px solid #eee;
margin-bottom: 0.5rem;
border-radius: 4px;
}
.theme-light {
background: white;
color: #212529;
}
.theme-dark {
background: #212529;
color: #f8f9fa;
}
.todo-item {
display: flex;
align-items: center;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
.todo-item.completed {
opacity: 0.6;
}
.todo-item.completed span {
text-decoration: line-through;
}
</style>
</head>
<body x-data="mainApp()">
<h1>Alpine.js State Management</h1>
<!-- Global Store Monitor -->
<div class="card">
<h2>Global Store Monitor</h2>
<div class="store-monitor">
<div>Cart Items: <span x-text="cart.items.length"></span></div>
<div>Cart Total: $<span x-text="cart.total.toFixed(2)"></span></div>
<div>User Theme: <span x-text="user.theme"></span></div>
<div>Todos: <span x-text="todos.items.filter(t => !t.completed).length"></span> pending, <span x-text="todos.items.filter(t => t.completed).length"></span> completed</div>
<div>Current Time: <span x-text="clock.time"></span></div>
</div>
</div>
<!-- Shopping Cart Store -->
<div class="card" x-data="cartStore()">
<h2>Shopping Cart Store</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
<!-- Products -->
<div>
<h3>Available Products</h3>
<template x-for="product in availableProducts" :key="product.id">
<div class="cart-item">
<div>
<strong x-text="product.name"></strong>
<span style="color: #6c757d;"> - $<span x-text="product.price"></span></span>
</div>
<button @click="addToCart(product)" class="btn">Add to Cart</button>
</div>
</template>
</div>
<!-- Cart -->
<div>
<h3>Shopping Cart</h3>
<template x-for="item in cart.items" :key="item.id">
<div class="cart-item">
<div>
<strong x-text="item.name"></strong>
<span style="color: #6c757d;"> ($<span x-text="item.price"></span> × <span x-text="item.quantity"></span>)</span>
</div>
<div>
<button @click="decrementQuantity(item.id)" class="btn" style="margin-right: 0.25rem;">-</button>
<span x-text="item.quantity" style="margin: 0 0.5rem;"></span>
<button @click="incrementQuantity(item.id)" class="btn" style="margin-right: 0.25rem;">+</button>
<button @click="removeFromCart(item.id)" class="btn danger">×</button>
</div>
</div>
</template>
<div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
<strong>Total: $<span x-text="cart.total.toFixed(2)"></span></strong>
</div>
<button @click="clearCart()" class="btn warning" style="margin-top: 0.5rem;">Clear Cart</button>
</div>
</div>
</div>
<!-- User Store -->
<div class="card" x-data="userStore()">
<h2>User Store</h2>
<div>
<h3>User Profile</h3>
<p>Name: <strong x-text="user.name"></strong></p>
<p>Email: <strong x-text="user.email"></strong></p>
<p>Theme: <strong x-text="user.theme"></strong></p>
<p>Login Count: <strong x-text="user.loginCount"></strong></p>
<div style="margin-top: 1rem;">
<button @click="toggleTheme()" class="btn">Toggle Theme</button>
<button @click="incrementLoginCount()" class="btn" style="margin-left: 0.5rem;">Increment Login</button>
</div>
</div>
<div style="margin-top: 2rem;">
<h3>Preferences</h3>
<label>
<input type="checkbox" x-model="user.preferences.notifications" style="margin-right: 0.5rem;">
Enable Notifications
</label><br>
<label style="margin-top: 0.5rem;">
<input type="checkbox" x-model="user.preferences.darkMode" style="margin-right: 0.5rem;">
Dark Mode (overrides theme)
</label><br>
<label style="margin-top: 0.5rem;">
Language:
<select x-model="user.preferences.language" style="margin-left: 0.5rem;">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
</label>
</div>
</div>
<!-- Todo Store -->
<div class="card" x-data="todoStore()">
<h2>Todo Store</h2>
<div>
<!-- Add Todo -->
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem;">
<input
type="text"
x-model="newTodo"
@keyup.enter="addTodo()"
placeholder="Add new todo..."
style="flex: 1; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px;">
<button @click="addTodo()" class="btn">Add</button>
</div>
<!-- Filters -->
<div style="margin-bottom: 1rem;">
<button
@click="todos.filter = 'all'"
:class="{ 'btn': true, 'success': todos.filter === 'all' }"
style="margin-right: 0.25rem;">
All (<span x-text="todos.items.length"></span>)
</button>
<button
@click="todos.filter = 'active'"
:class="{ 'btn': true, 'success': todos.filter === 'active' }"
style="margin-right: 0.25rem;">
Active (<span x-text="todos.items.filter(t => !t.completed).length"></span>)
</button>
<button
@click="todos.filter = 'completed'"
:class="{ 'btn': true, 'success': todos.filter === 'completed' }"
style="margin-right: 0.25rem;">
Completed (<span x-text="todos.items.filter(t => t.completed).length"></span>)
</button>
</div>
<!-- Todo List -->
<template x-for="todo in filteredTodos" :key="todo.id">
<div class="todo-item" :class="{ completed: todo.completed }">
<input
type="checkbox"
x-model="todo.completed"
@change="toggleTodo(todo.id)"
style="margin-right: 0.5rem;">
<span x-text="todo.text" style="flex: 1;"></span>
<button
@click="removeTodo(todo.id)"
class="btn danger"
style="padding: 0.25rem 0.5rem; font-size: 0.875rem;">
Delete
</button>
</div>
</template>
<!-- Stats -->
<div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
<div>Completion Rate: <strong x-text="Math.round((todos.items.filter(t => t.completed).length / todos.items.length) * 100) || 0">%</strong></div>
</div>
</div>
</div>
<!-- Clock Store -->
<div class="card" x-data="clockStore()">
<h2>Clock Store</h2>
<div style="text-align: center;">
<div style="font-size: 3rem; font-weight: bold; margin-bottom: 1rem;">
<span x-text="clock.time"></span>
</div>
<div style="margin-bottom: 1rem;">
<button @click="toggleClock()" class="btn">
<span x-text="clock.isRunning ? 'Stop' : 'Start'"></span> Clock
</button>
<button @click="resetClock()" class="btn" style="margin-left: 0.5rem;">Reset</button>
</div>
<div>
<label>
<input type="checkbox" x-model="clock.showSeconds" style="margin-right: 0.5rem;">
Show Seconds
</label>
</div>
</div>
</div>
<!-- Cross-Store Communication -->
<div class="card" x-data="crossStoreDemo()">
<h2>Cross-Store Communication</h2>
<div>
<p>This demonstrates how different stores can communicate and affect each other.</p>
<div style="margin-top: 1rem;">
<button @click="performComplexAction()" class="btn">
Perform Complex Action (Add Random Product + Todo)
</button>
<button @click="resetAllStores()" class="btn warning" style="margin-left: 0.5rem;">
Reset All Stores
</button>
</div>
<div style="margin-top: 1rem; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
<strong>Action Log:</strong>
<ul style="margin: 0.5rem 0 0 0; padding-left: 1rem;">
<template x-for="log in actionLogs" :key="log.timestamp">
<li style="font-size: 0.875rem; color: #6c757d;">
<span x-text="log.timestamp"></span> - <span x-text="log.message"></span>
</li>
</template>
</ul>
</div>
</div>
</div>
<script>
// Alpine Store Implementation
document.addEventListener('alpine:init', () => {
// Shopping Cart Store
Alpine.store('cart', {
items: [],
get total() {
return this.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
add(product) {
const existingItem = this.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity++;
} else {
this.items.push({ ...product, quantity: 1 });
}
},
remove(productId) {
this.items = this.items.filter(item => item.id !== productId);
},
incrementQuantity(productId) {
const item = this.items.find(item => item.id === productId);
if (item) {
item.quantity++;
}
},
decrementQuantity(productId) {
const item = this.items.find(item => item.id === productId);
if (item && item.quantity > 1) {
item.quantity--;
} else {
this.remove(productId);
}
},
clear() {
this.items = [];
}
});
// User Store
Alpine.store('user', {
name: 'John Doe',
email: '[email protected]',
theme: 'light',
loginCount: 1,
preferences: {
notifications: true,
darkMode: false,
language: 'en'
},
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
},
incrementLoginCount() {
this.loginCount++;
}
});
// Todo Store
Alpine.store('todos', {
items: [],
filter: 'all',
add(text) {
this.items.push({
id: Date.now(),
text,
completed: false,
createdAt: new Date().toISOString()
});
},
toggle(id) {
const todo = this.items.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
},
remove(id) {
this.items = this.items.filter(t => t.id !== id);
},
get filtered() {
switch(this.filter) {
case 'active':
return this.items.filter(t => !t.completed);
case 'completed':
return this.items.filter(t => t.completed);
default:
return this.items;
}
}
});
// Clock Store
Alpine.store('clock', {
time: new Date().toLocaleTimeString(),
isRunning: true,
showSeconds: true,
interval: null,
init() {
this.start();
},
start() {
this.isRunning = true;
this.interval = setInterval(() => {
this.updateTime();
}, 1000);
},
stop() {
this.isRunning = false;
if (this.interval) {
clearInterval(this.interval);
}
},
toggle() {
if (this.isRunning) {
this.stop();
} else {
this.start();
}
},
updateTime() {
const now = new Date();
if (this.showSeconds) {
this.time = now.toLocaleTimeString();
} else {
this.time = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
},
reset() {
this.time = '00:00:00';
}
});
});
// Component data functions
function mainApp() {
return {
cart: Alpine.store('cart'),
user: Alpine.store('user'),
todos: Alpine.store('todos'),
clock: Alpine.store('clock')
}
}
function cartStore() {
return {
cart: Alpine.store('cart'),
availableProducts: [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Mouse', price: 29 },
{ id: 3, name: 'Keyboard', price: 79 },
{ id: 4, name: 'Monitor', price: 299 },
{ id: 5, name: 'Headphones', price: 149 }
],
addToCart(product) {
this.cart.add(product);
},
removeFromCart(productId) {
this.cart.remove(productId);
},
incrementQuantity(productId) {
this.cart.incrementQuantity(productId);
},
decrementQuantity(productId) {
this.cart.decrementQuantity(productId);
},
clearCart() {
this.cart.clear();
}
}
}
function userStore() {
return {
user: Alpine.store('user'),
toggleTheme() {
this.user.toggleTheme();
},
incrementLoginCount() {
this.user.incrementLoginCount();
}
}
}
function todoStore() {
return {
newTodo: '',
todos: Alpine.store('todos'),
filteredTodos: [],
init() {
this.$watch('todos.filter', () => {
this.filteredTodos = this.todos.filtered;
});
this.filteredTodos = this.todos.filtered;
},
addTodo() {
if (this.newTodo.trim()) {
this.todos.add(this.newTodo);
this.newTodo = '';
}
},
toggleTodo(id) {
this.todos.toggle(id);
},
removeTodo(id) {
this.todos.remove(id);
}
}
}
function clockStore() {
return {
clock: Alpine.store('clock'),
toggleClock() {
this.clock.toggle();
},
resetClock() {
this.clock.reset();
}
}
}
function crossStoreDemo() {
return {
actionLogs: [],
cart: Alpine.store('cart'),
todos: Alpine.store('todos'),
user: Alpine.store('user'),
performComplexAction() {
// Add random product to cart
const products = [
{ id: Date.now(), name: 'Random Product ' + Math.floor(Math.random() * 100), price: Math.floor(Math.random() * 100) + 10 }
];
this.cart.add(products[0]);
this.logAction('Added random product to cart');
// Add todo
this.todos.add('Complex action performed - check cart');
this.logAction('Added todo about complex action');
// Increment user login count
this.user.incrementLoginCount();
this.logAction('Incremented user login count');
},
resetAllStores() {
this.cart.clear();
this.todos.items = [];
this.todos.filter = 'all';
this.user.loginCount = 0;
this.logAction('Reset all stores');
},
logAction(message) {
this.actionLogs.unshift({
timestamp: new Date().toLocaleTimeString(),
message
});
// Keep only last 10 logs
if (this.actionLogs.length > 10) {
this.actionLogs.pop();
}
}
}
}
</script>
</body>
</html>