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:

bash
npm install alpinejs

Kedua, impor dan nyalakan di resources/js/app.js:

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.

Contoh pertama kita, menu akun yang bisa dibuka-tutup:

blade
{{-- 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:

php
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:

text
  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:

blade
<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:

text
<div x-data='{ filter: "", daftar: [{"id":1,"judul":"Belajar Vite"},{"id":2,"judul":"Rakit UI kit"}] }'>

Kapan Alpine Cukup, Kapan Tidak #

SituasiPilihan tepat
Dropdown, modal, tab, accordionAlpine
Form multi-langkah dengan validasi kompleksPertimbangkan Livewire
Daftar yang tersaring real-time dari databaseLivewire (wire:model)
Halaman dengan state klien dan server yang saling tergantungLivewire 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.