Alpine.js Basics
What is Alpine.js?
Alpine.js is a lightweight JavaScript framework for adding interactivity to HTML.
Basic Syntax
<!-- x-data: Initialize component -->
<div x-data="{ count: 0 }">
<button @click="count++">Count: <span x-text="count"></span></button>
</div>
Directives
| Directive | Purpose |
|---|---|
x-data |
Initialize component state |
x-bind (:) |
Bind attribute |
x-on (@) |
Event listener |
x-text |
Set text content |
x-html |
Set HTML content |
x-show |
Show/hide element |
x-if |
Add/remove element |
x-for |
Loop through array |
x-model |
Two-way binding |
x-init |
Initialize on load |
Complete Example
<div x-data="{ items: [], newItem: '' }" x-init="items = await fetch('/api/items').then(r => r.json())">
<input type="text" x-model="newItem">
<button @click="items.push(newItem); newItem = ''">Add</button>
<template x-for="item in items" :key="item">
<div x-text="item"></div>
</template>
</div>
Reactive UI Patterns
Toggle Component
<div x-data="{ open: false }">
<button @click="open = !open" x-text="open ? 'Close' : 'Open'">
</button>
<div x-show="open" x-transition>
Toggle content
</div>
</div>
Form Handling
<div x-data="{
form: { email: '', password: '' },
loading: false,
async submit() {
this.loading = true;
await fetch('/login', {
method: 'POST',
body: JSON.stringify(this.form)
});
this.loading = false;
}
}">
<form @submit.prevent="submit">
<input type="email" x-model="form.email">
<input type="password" x-model="form.password">
<button type="submit" :disabled="loading">
<span x-show="!loading">Login</span>
<span x-show="loading">Loading...</span>
</button>
</form>
</div>
Shopping Cart
<div x-data="{
cart: [],
get total() {
return this.cart.reduce((sum, item) => sum + item.price * item.qty, 0);
},
add(product) {
this.cart.push({...product, qty: 1});
},
remove(index) {
this.cart.splice(index, 1);
}
}">
<div x-text="'Cart: ' + cart.length + ' items'"></div>
<div x-text="'Total: $' + total.toFixed(2)"></div>
<template x-for="(item, index) in cart" :key="index">
<div>
<span x-text="item.name"></span>
<button @click="remove(index)">Remove</button>
</div>
</template>
</div>
Component Patterns
Dropdown Component
<div x-data="{ open: false }" @click.away="open = false">
<button @click="open = !open">
Menu
<svg x-show="open" class="rotate-180">...</svg>
</button>
<div x-show="open"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95">
Menu items
</div>
</div>
Modal Component
<div x-data="{ open: false }">
<button @click="open = true">Open Modal</button>
<div x-show="open" class="fixed inset-0 bg-black bg-opacity-50">
<div class="bg-white rounded p-6 max-w-md mx-auto mt-20">
<h2 class="text-xl font-bold">Modal Title</h2>
<p>Modal content</p>
<button @click="open = false">Close</button>
</div>
</div>
</div>
Tab Component
<div x-data="{ activeTab: 'tab1' }">
<div class="flex border-b">
<button @click="activeTab = 'tab1'"
:class="activeTab === 'tab1' ? 'border-b-2 border-blue-500' : ''">
Tab 1
</button>
<button @click="activeTab = 'tab2'"
:class="activeTab === 'tab2' ? 'border-b-2 border-blue-500' : ''">
Tab 2
</button>
</div>
<div x-show="activeTab === 'tab1'">Content 1</div>
<div x-show="activeTab === 'tab2'">Content 2</div>
</div>
Migrating from KnockoutJS
KnockoutJS to Alpine.js
| KnockoutJS | Alpine.js |
|---|---|
ko.observable() |
x-data property |
ko.computed() |
Getter in x-data |
data-bind="text" |
x-text |
data-bind="visible" |
x-show |
data-bind="click" |
@click |
data-bind="foreach" |
x-for |
data-bind="value" |
x-model |
Migration Example
KnockoutJS
<div data-bind="scope: 'cart'">
<span data-bind="text: items().length"></span>
<button data-bind="click: addItem">Add</button>
</div>
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"component": "cart"
}
}
}
</script>
Alpine.js
<div x-data="{ items: [], addItem() { this.items.push({}) } }">
<span x-text="items.length"></span>
<button @click="addItem">Add</button>
</div>
Key Differences
- No build step: Alpine.js works directly in HTML
- No components: State lives in
x-data - Simpler syntax: Directives are intuitive
- Lighter weight: ~15KB vs 500KB+
Quiz
1. What directive initializes Alpine.js state?
2. What replaces KnockoutJS data-bind="text"?
3. How do you handle form submission?
Flashcards
Question
What is Alpine.js?
Click to reveal answer
Answer
A lightweight JavaScript framework for adding interactivity
Question
What initializes Alpine state?
Click to reveal answer
Answer
x-data directive
Question
How do you bind events?
Click to reveal answer
Answer
@click, @submit, @change, etc.
Question
How do you show/hide elements?
Click to reveal answer
Answer
x-show directive
Question
How do you loop through arrays?
Click to reveal answer
Answer
x-for directive
Revision Notes
Key Takeaways
- 1. Alpine.js provides reactive UI without build step
- 2. x-data initializes component state
- 3. Directives replace KnockoutJS bindings
- 4. No RequireJS or build tools required
- 5. Much lighter than KnockoutJS
Interview Tips
- • Explain Alpine.js vs KnockoutJS differences
- • Know the common Alpine.js directives
- • Discuss migration patterns
- • Be ready to create Alpine.js components
Cheat Sheet
Directives:
x-data: Initialize state
x-text: Set text
x-html: Set HTML
x-show: Show/hide
x-for: Loop
x-model: Two-way binding
@click: Event listener
Migration:
data-bind="text" → x-text
data-bind="visible" → x-show
data-bind="foreach" → x-for