Alpine.js Complete Guide | Lightweight JavaScript Framework
이 글의 핵심
Alpine.js is a lightweight JavaScript framework that offers reactive and declarative nature of Vue.js at a fraction of the cost. Perfect for adding interactivity to server-rendered HTML.
Introduction
Alpine.js is a rugged, minimal framework for composing JavaScript behavior in your markup. It offers the reactive and declarative nature of big frameworks like Vue or React at a much lower cost.
The Problem
Traditional approach with vanilla JavaScript:
<button id="toggle">Toggle</button>
<div id="content" style="display: none;">Content</div>
<script>
const button = document.getElementById('toggle');
const content = document.getElementById('content');
let isOpen = false;
button.addEventListener('click', () => {
isOpen = !isOpen;
content.style.display = isOpen ? 'block' : 'none';
});
</script>
The Solution
With Alpine.js:
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>
1. Installation
CDN (Quick Start)
<!DOCTYPE html>
<html>
<head>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
<div x-data="{ count: 0 }">
<button @click="count++">Increment</button>
<span x-text="count"></span>
</div>
</body>
</html>
NPM
npm install alpinejs
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
2. Core Directives
x-data (State)
<div x-data="{ name: 'John', age: 30 }">
<p x-text="name"></p>
<p x-text="age"></p>
</div>
x-show / x-if (Conditional)
<!-- x-show: toggles display CSS -->
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">I'm visible!</div>
</div>
<!-- x-if: adds/removes from DOM -->
<template x-if="user">
<div x-text="user.name"></div>
</template>
x-for (Loop)
<div x-data="{ items: ['Apple', 'Banana', 'Orange'] }">
<template x-for="item in items" :key="item">
<li x-text="item"></li>
</template>
</div>
x-on / @ (Events)
<div x-data="{ count: 0 }">
<!-- Long form -->
<button x-on:click="count++">Increment</button>
<!-- Short form -->
<button @click="count++">Increment</button>
<!-- With modifiers -->
<form @submit.prevent="handleSubmit">
<input @keyup.enter="submit">
</form>
</div>
x-bind / : (Attributes)
<div x-data="{ isActive: true, color: 'blue' }">
<!-- Long form -->
<div x-bind:class="{ active: isActive }"></div>
<!-- Short form -->
<div :class="{ active: isActive }"></div>
<!-- Multiple attributes -->
<div :class="isActive && 'active'" :style="`color: ${color}`"></div>
</div>
x-model (Two-way Binding)
<div x-data="{ search: '' }">
<input type="text" x-model="search" placeholder="Search...">
<p>Searching for: <span x-text="search"></span></p>
</div>
x-text / x-html
<div x-data="{ message: 'Hello World', html: '<strong>Bold</strong>' }">
<!-- Text content -->
<p x-text="message"></p>
<!-- HTML content -->
<div x-html="html"></div>
</div>
3. Practical Examples
Dropdown Menu
<div x-data="{ open: false }" @click.away="open = false">
<button @click="open = !open">Menu</button>
<div x-show="open" x-transition>
<a href="#">Profile</a>
<a href="#">Settings</a>
<a href="#">Logout</a>
</div>
</div>
Tabs
<div x-data="{ tab: 'home' }">
<nav>
<button @click="tab = 'home'" :class="{ active: tab === 'home' }">
Home
</button>
<button @click="tab = 'profile'" :class="{ active: tab === 'profile' }">
Profile
</button>
</nav>
<div x-show="tab === 'home'">Home content</div>
<div x-show="tab === 'profile'">Profile content</div>
</div>
Modal
<div x-data="{ open: false }">
<button @click="open = true">Open Modal</button>
<div x-show="open"
x-transition
@click.away="open = false"
class="modal">
<div class="modal-content">
<h2>Modal Title</h2>
<p>Modal content</p>
<button @click="open = false">Close</button>
</div>
</div>
</div>
Search Filter
<div x-data="{
search: '',
items: ['Apple', 'Banana', 'Cherry', 'Date'],
get filteredItems() {
return this.items.filter(i =>
i.toLowerCase().includes(this.search.toLowerCase())
);
}
}">
<input type="text" x-model="search" placeholder="Search fruits...">
<template x-for="item in filteredItems" :key="item">
<li x-text="item"></li>
</template>
</div>
4. Advanced Features
Alpine.data (Component)
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
open: false,
toggle() {
this.open = !this.open;
}
}));
});
</script>
<div x-data="dropdown">
<button @click="toggle">Toggle</button>
<div x-show="open">Dropdown content</div>
</div>
$watch (Reactive)
<div x-data="{ count: 0 }" x-init="$watch('count', value => console.log('Count:', value))">
<button @click="count++">Increment</button>
</div>
$refs (DOM Access)
<div x-data="{}">
<input x-ref="input" type="text">
<button @click="$refs.input.focus()">Focus Input</button>
</div>
5. Transitions
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<!-- Simple transition -->
<div x-show="open" x-transition>
Fade in/out
</div>
<!-- Custom transition -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform scale-90"
x-transition:enter-end="opacity-100 transform scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform scale-100"
x-transition:leave-end="opacity-0 transform scale-90">
Custom animation
</div>
</div>
6. Ajax Example
<div x-data="{
users: [],
loading: false,
async fetchUsers() {
this.loading = true;
const res = await fetch('/api/users');
this.users = await res.json();
this.loading = false;
}
}" x-init="fetchUsers()">
<div x-show="loading">Loading...</div>
<template x-for="user in users" :key="user.id">
<div x-text="user.name"></div>
</template>
</div>
7. Best Practices
1. Keep State Close
<!-- Good: state is scoped to component -->
<div x-data="{ count: 0 }">
<button @click="count++">Increment</button>
<span x-text="count"></span>
</div>
<!-- Bad: global state -->
<script>
let globalCount = 0;
</script>
2. Use Alpine.data for Reusability
Alpine.data('counter', (initial = 0) => ({
count: initial,
increment() {
this.count++;
}
}));
<div x-data="counter(5)">
<button @click="increment">Increment</button>
<span x-text="count"></span>
</div>
3. Use x-cloak to Prevent Flash
[x-cloak] { display: none !important; }
<div x-data="{ show: false }" x-cloak>
<!-- Won't flash before Alpine loads -->
</div>
8. Framework Comparison
| Feature | Alpine.js | React | Vue |
|---|---|---|---|
| Size | 15KB | 40KB | 34KB |
| Learning Curve | Easy | Medium | Medium |
| Use Case | Sprinkles | SPA | SPA |
| Build Required | No | Yes | Optional |
Summary
Alpine.js is perfect for adding interactivity to server-rendered pages:
- Lightweight - only 15KB
- No build step - works with CDN
- Declarative - Vue-like syntax
- Progressive - enhance existing HTML
- Simple - learn in minutes
Key Takeaways:
- Use
x-datato define component state - Use
@clickfor event handling - Use
x-show/x-iffor conditionals - Use
x-forfor loops - Perfect for dropdowns, modals, tabs
Next Steps:
Resources: