Modal di chapter UI kit tadi bergerak dengan dua directive misterius: x-data dan x-show. Saatnya membuka kotak itu. Alpine.js adalah framework JavaScript mungil — pada pengujian kami menambah sekitar 55 kB ke hasil build — untuk interaktivitas yang tersebar (sprinkles): dropdown, tab, accordion, modal. Filosofinya mengingatkan kita pada jQuery dari sisi ergonomi tulis-di-markup, tapi dengan model data reaktif ala framework modern. Karena sudah terbundling lewat Vite, tidak ada file baru, tidak ada konfigurasi — hanya npm install dan dua baris impor.
Filosofi: Framework untuk Sprinkles #
Sebelum menulis kode, letakkan Alpine di peta yang benar. Vue dan React membangun aplikasi: seluruh halaman adalah pohon komponen JavaScript yang mengambil alih DOM. Alpine hanya menyiram halaman Blade: direktif x-* menempel di markup yang tetap dirender server-side, dan JavaScript hanya mengelola potongan-potongan interaktif. Situs resminya di alpinejs.dev memperkenalkan diri sebagai “rugged, minimal tool for composing behavior directly in your markup. Think of it like jQuery for the modern web” — seluruh frameworknya terdiri dari 15 atribut, 6 properti, dan 2 method. Konsekuensinya, halaman tetap SEO-friendly dan berfungsi sebagai HTML utuh, sementara dropdown dan modal mendapat perilaku hidup.
Instalasi via npm #
Dua langkah, keduanya kami jalankan di sandbox. Pertama, pasang package:
npm install alpinejsKedua, impor dan nyalakan di resources/js/app.js:
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();Baris window.Alpine membuka akses dari DevTools dan plugin pihak ketiga; Alpine.start() memindai DOM dan mengaktifkan semua direktif. Karena app.js sudah terdaftar di @vite, selesai sudah instalasinya. Efeknya terlihat langsung di hasil build: chunk JavaScript utama tumbuh dari 10.98 kB menjadi 66.10 kB — itulah harga framework ini, dan tetap jauh lebih ringan daripada SPA penuh.
Dropdown: x-data, x-show, x-on #
Contoh pertama kita, menu akun yang bisa dibuka-tutup:
{{-- resources/views/latihan-alpine.blade.php (potongan) --}}
<div class="relative" x-data="{ terbuka: false }">
<x-ui.button x-on:click="terbuka = !terbuka">Akun Budi</x-ui.button>
<div
x-show="terbuka"
style="display: none"
x-on:click.outside="terbuka = false"
class="absolute left-0 mt-2 w-40 rounded-lg bg-white shadow-lg ring-1 ring-slate-200"
>
<a href="#" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">Profil</a>
<a href="#" class="block px-4 py-2 text-sm text-rose-600 hover:bg-rose-50">Keluar</a>
</div>
</div>Tiga direktif inti bekerja sama di sini. x-data="{ terbuka: false }" mendeklarasikan state dan cakupan sekaligus — semua direktif di dalam <div> ini bisa membaca terbuka. x-on:click="terbuka = !terbuka" membalik nilai state saat tombol diklik (Alpine menyediakan singkatannya: @click). x-show="terbuka" menampilkan elemen hanya saat state bernilai true, dengan style="display: none" sebagai pengaman agar menu tidak berkedip sebelum JavaScript jalan. Modifier .outside menutup menu saat klik jatuh di luar elemen — perilaku yang dulu butuh listener global, kini satu kata.
Semua ini ter-render di HTML awal yang dikirim server — feature test memeriksa kehadiran direktifnya tanpa browser:
public function test_halaman_alpine_memuat_directive_dan_data_server(): void
{
$response = $this->get('/latihan-alpine');
$html = $response->getContent();
$this->assertStringContainsString('x-data="{ terbuka: false }"', $html);
$this->assertStringContainsString('x-on:click="terbuka = !terbuka"', $html);
$this->assertStringContainsString('x-show="terbuka"', $html);
}Output yang Dihasilkan:
PASS Tests\Feature\AlpineTest
✓ halaman alpine memuat directive dan data server 0.16s
Tests: 1 passed (9 assertions)x-model dan x-for: Dua Arah dan Daftar #
Kekuatan Alpine muncul saat state mulai mengalir. Input pencarian di halaman yang sama memakai x-model untuk two-way binding dan <template x-for> untuk merender daftar:
<div x-data='{ filter: "", daftar: @json($tugas) }'>
<input
type="text"
x-model="filter"
placeholder="Cari tugas..."
class="w-full rounded-lg px-3 py-2 text-sm ring-1 ring-slate-300"
>
<ul class="mt-3 space-y-1">
<template
x-for="tugas in daftar.filter(t => t.judul.toLowerCase().includes(filter.toLowerCase()))"
:key="tugas.id"
>
<li class="rounded-lg px-3 py-2 text-sm text-slate-700 ring-1 ring-slate-200"
x-text="tugas.judul"></li>
</template>
</ul>
</div>x-model="filter" mengikat nilai input ke state filter — ketik satu huruf, state berubah. Ekspresi di x-for merespons state itu: daftar tugas tersaring begitu pengguna mengetik, semuanya di sisi klien tanpa satu request pun. Direktif x-text mengisi teks elemen dari state, kebalikan arah aliran x-model.
Perhatikan satu detail penting di blok di atas: atribut x-data dibungkus kutip tunggal, sementara JSON di dalamnya memakai kutip ganda. Itu bukan selera — itu keharusan.
Data Server Masuk lewat @json #
Data tugas di atas datang dari route (array PHP), lalu diteruskan ke Alpine lewat @json($tugas). Blade merender directive ini menjadi JSON mentah:
<div x-data='{ filter: "", daftar: [{"id":1,"judul":"Belajar Vite"},{"id":2,"judul":"Rakit UI kit"}] }'>Kapan Alpine Cukup, Kapan Tidak #
| Situasi | Pilihan tepat |
|---|---|
| Dropdown, modal, tab, accordion | Alpine |
| Form multi-langkah dengan validasi kompleks | Pertimbangkan Livewire |
| Daftar yang tersaring real-time dari database | Livewire (wire:model) |
| Halaman dengan state klien dan server yang saling tergantung | Livewire atau Inertia |
| Aplikasi dashboard interaktif penuh (drag-drop, realtime canvas) | Vue/React via Inertia |
Aturan praktisnya: selama state hanya hidup di browser dan datanya sudah terkirim saat render awal, Alpine menang telak — ringan, tanpa round-trip, tanpa konsep baru. Begitu tiap ketikan harus berbicara dengan server, kita membutuhkan framework yang mengelola perjalanan bolak-balik itu, dan di ekosistem Laravel jawabnya bernama Livewire — pengenalan kita di chapter berikutnya.