Kombinasi Laravel 13, Livewire 4, dan Flux UI merupakan salah satu stack paling modern dan efisien untuk membangun aplikasi web interaktif tanpa harus keluar dari ekosistem PHP.
Flux UI adalah library komponen UI resmi yang dirancang oleh Caleb Porzio (pencipta Livewire). Dengan Flux, kita bisa membuat antarmuka yang sangat indah, responsif, dan kaya fitur (seperti modal, sidebar, tabel, dll.) dengan sintaksis HTML-like yang bersih.
Dalam panduan ini, kita akan belajar langkah demi langkah membangun fitur CRUD Category (Manajemen Kategori) secara lengkap dan rapi.
Prasyarat & Persiapan Awal
Sebelum memulai, pastikan Anda telah menginstal proyek Laravel baru, mengonfigurasi database, serta memasang Livewire 3 dan Flux UI.
Jika semua sudah siap, pastikan database sudah dimigrasi dan server aset Anda berjalan:
# Menjalankan migrasi awal database
php artisan migrate
# Menjalankan build tools (Vite/Tailwind)
npm run dev
Langkah 1: Membuat Model dan Migration Category
Pertama, kita butuh tabel di database untuk menyimpan data kategori. Kita bisa membuat model Eloquent sekaligus file migrasinya dengan satu perintah:
php artisan make:model Category -m
Perintah di atas akan menghasilkan dua file penting:
- Model:
app/Models/Category.php - Migration:
database/migrations/xxxx_xx_xx_create_categories_table.php
1. Konfigurasi Migration
Buka file migration yang baru dibuat, lalu tambahkan kolom name dan description seperti di bawah ini:
// database/migrations/xxxx_xx_xx_create_categories_table.php
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->text('description')->nullable();
$table->timestamps();
});
2. Konfigurasi Model
Buka file model Category.php dan tambahkan properti $fillable agar kolom tersebut bisa diisi secara massal (mass assignment):
// app/Models/Category.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $fillable = ['name', 'description'];
}
Setelah itu, jalankan migrasi untuk membuat tabel categories di database Anda:
php artisan migrate
Langkah 2: Membuat Komponen Livewire Index
Untuk menampilkan daftar kategori, kita akan membuat sebuah komponen Livewire khusus halaman (Full-page Component). Pada Livewire 4, format default yang digunakan adalah Single-File Component (SFC) di mana logika PHP dan markup HTML disatukan dalam satu file Blade tunggal untuk mengurangi boilerplate.
Jalankan perintah berikut untuk membuatnya:
php artisan make:livewire pages::category.index
Perintah di atas akan menghasilkan satu file komponen tunggal:
- File SFC:
resources/views/livewire/pages/category/index.blade.php
Mendaftarkan Route
Agar halaman ini bisa diakses lewat browser, tambahkan route baru di file routes/web.php menggunakan directive Route::livewire():
// routes/web.php
use Illuminate\Support\Facades\Route;
Route::livewire('/categories', 'pages::category.index')
->middleware(['auth'])
->name('category.index');
Langkah 3: Membuat Livewire Form Object (CategoryForm)
Praktik terbaik di Livewire 3 untuk menangani input dan validasi formulir adalah menggunakan Form Object. Ini menjaga agar class komponen utama tetap bersih dan fokus pada logika halaman.
Buat Form Object baru dengan perintah:
php artisan livewire:form CategoryForm
Buka file app/Livewire/Forms/CategoryForm.php yang dihasilkan, lalu sesuaikan isinya untuk menangani proses validasi dan penyimpanan:
<?php
namespace App\Livewire\Forms;
use App\Models\Category;
use Illuminate\Validation\Rule;
use Livewire\Form;
class CategoryForm extends Form
{
public string $name = '';
public string $description = '';
public ?Category $category = null;
public function rules(): array
{
return [
'name' => [
'required',
'string',
'min:3',
'max:255',
Rule::unique('categories', 'name')->ignore($this->category?->id),
],
'description' => [
'nullable',
'string',
'max:1000',
],
];
}
public function setCategory(Category $category): void
{
$this->category = $category;
$this->name = $category->name;
$this->description = $category->description ?? '';
}
public function store()
{
$this->validate();
Category::create($this->only(['name', 'description']));
$this->reset();
}
// update
public function update()
{
$this->validate();
$this->category->update($this->only(['name', 'description']));
}
}
Langkah 4: Menulis Logika Komponen Livewire Index
Buka file komponen utama di resources/views/livewire/pages/category/index.blade.php. Karena komponen ini dibuat sebagai Single-File Component, pertama kita akan menulis blok logika PHP (<?php ... ?>) di bagian paling atas file tersebut:
<?php
use Livewire\Component;
use Livewire\Attributes\Computed;
use Livewire\WithPagination;
use App\Models\Category;
new class extends Component
{
use WithPagination;
public $sortBy = 'name';
public $sortDirection = 'desc';
public function sort($column) {
if ($this->sortBy === $column) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $column;
$this->sortDirection = 'asc';
}
}
#[Computed]
public function categories()
{
// return Category::latest()->paginate(2);
return Category::query()
->tap(fn ($query) => $this->sortBy ? $query->orderBy($this->sortBy, $this->sortDirection) : $query)
->paginate(5);
}
public function edit($id){
$this->dispatch('edit-category', id: $id);
}
};
?>
Langkah 5: Mendesain UI Modern dengan Flux UI
Tepat di bawah blok PHP (?>) pada file komponen resources/views/livewire/pages/category/index.blade.php yang sama, tuliskan kode Blade berikut untuk menyusun tampilan antarmuka (UI) menggunakan Flux UI:
<div class="max-w-7xl mx-auto space-y-4">
<flux:heading size="xl" class="text-zinc-800 dark:text-white">Category</flux:heading>
<flux:subheading size="lg" class="text-zinc-600 dark:text-zinc-400">Manage your categories</flux:subheading>
<flux:separator variant="subtle" />
<!-- modal -->
<flux:modal.trigger name="create-category">
<flux:button variant="primary" icon="plus" color="primary">Add Category</flux:button>
</flux:modal.trigger>
<livewire:category.create />
<livewire:category.edit />
<x-flash-message />
{{-- table --}}
<div class="overflow-x-auto">
<flux:table :paginate="$this->categories">
<flux:table.columns>
<flux:table.column sortable :sorted="$sortBy === 'name'" :direction="$sortDirection" wire:click="sort('name')">Name</flux:table.column>
<flux:table.column>Description</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'created_at'" :direction="$sortDirection" wire:click="sort('created_at')">Created At</flux:table.column>
<flux:table.column></flux:table.column>
</flux:table.columns>
<flux:table.rows>
@foreach ($this->categories as $category)
<flux:table.row :key="$category->id">
<flux:table.cell class="flex items-center gap-3">
{{ $category->name }}
</flux:table.cell>
<flux:table.cell class="text-zinc-500 dark:text-zinc-400">
{{ $category->description ?? '-' }}
</flux:table.cell>
<flux:table.cell class="whitespace-nowrap">{{ $category->created_at->diffForHumans() }}</flux:table.cell>
<flux:table.cell>
<flux:dropdown>
<flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom"></flux:button>
<flux:menu>
<flux:menu.item icon="pencil" wire:click="edit({{ $category->id }})">Edit</flux:menu.item>
<flux:menu.separator />
{{-- <flux:menu.item variant="danger" icon="trash" wire:click="$dispatch('confirm-delete', id: $category->id)">Delete</flux:menu.item> --}}
<flux:menu.item variant="danger" icon="trash" wire:click="$dispatch('confirm-delete', {id: {{ $category->id }}})">Delete</flux:menu.item>
</flux:menu>
</flux:dropdown>
</flux:table.cell>
</flux:table.row>
@endforeach
</flux:table.rows>
</flux:table>
</div>
</div>
Langkah 6: Membuat Komponen Create Category (Single-File Component)
Selanjutnya, kita akan membuat komponen untuk menambahkan kategori baru. Komponen ini dirancang dalam bentuk modal dialog interaktif yang dipicu dari tombol Add Category pada halaman Index.
Dengan fitur Single-File Component (SFC) bawaan Livewire 4, seluruh logika PHP dan tampilan HTML (Blade) dapat disatukan di dalam satu file komponen tunggal. Buatlah file baru di resources/views/livewire/category/create.blade.php dengan perintah di bawah dan tambahkan kode berikut:
php artisan make:livewire category.create
<?php
use App\Livewire\Forms\CategoryForm;
use Livewire\Component;
new class extends Component
{
public CategoryForm $form;
public function save()
{
$this->form->store();
Flux::modal('create-category')->close();
// session
session()->flash('success', 'Category created successfully');
$this->redirectRoute('category.index',navigate: true);
}
public function resetForm()
{
$this->resetValidation();
$this->form->reset();
}
};
?>
<div>
<flux:modal
name="create-category"
class="md:w-150"
x-on:close="$wire.resetForm()"
>
<form class="space-y-8" wire:submit.prevent="save">
{{-- header --}}
<div class="space-y-2">
<flux:heading size="lg" class="text-zinc-900 dark:text-white">
Create Category
</flux:heading>
<flux:text class="text-zinc-500 dark:text-zinc-400">
Add a new category to your account
</flux:text>
</div>
{{-- form field --}}
<div class="space-y-6">
<flux:input
label="Name"
placeholder="Enter category name"
wire:model="form.name"
/>
<flux:textarea
label="Description"
placeholder="Enter category description"
wire:model="form.description"
/>
</div>
{{-- footer --}}
<div class="flex items-center justify-end gap-3 pt-4 border-t border-zinc-200 dark:border-zinc-800">
<flux:modal.close>
<flux:button variant="outline" color="neutral">Cancel</flux:button>
</flux:modal.close>
<flux:button variant="primary" color="primary" type="submit">Create</flux:button>
</div>
</form>
</flux:modal>
</div>
Langkah 7: Membuat Komponen Edit & Delete Category (Single-File Component)
Untuk melengkapi fungsionalitas CRUD secara menyeluruh, kita akan membuat satu komponen Single-File Component lagi yang bertugas menangani aksi pengubahan (Edit) dan penghapusan (Delete) kategori. Menggabungkan kedua aksi ini sangat efisien karena keduanya membutuhkan referensi data model kategori yang sama yang sedang dipilih oleh pengguna.
Buatlah file komponen baru di resources/views/livewire/category/edit.blade.php dengan perintah dan isi kode sebagai berikut:
php artisan make:livewire category.edit
<?php
use Livewire\Component;
use Livewire\Attributes\On;
use App\Livewire\Forms\CategoryForm;
use App\Models\Category;
new class extends Component
{
public CategoryForm $form;
#[On('edit-category')]
public function editCategory($id){
$category = Category::find($id);
$this->form->setCategory($category);
Flux::modal('edit-category')->show();
}
public function updateCategory() {
$this->form->update();
Flux::modal('edit-category')->close();
session()->flash('success', 'Category updated successfully');
$this->redirectRoute('category.index', navigate: true);
}
public function resetForm()
{
$this->resetValidation();
$this->form->reset();
}
#[On('confirm-delete')]
public function confirmDelete($id)
{
$category = Category::find($id);
$this->form->setCategory($category);
Flux::modal('delete-category')->show();
}
public function deleteCategory() {
$this->form->category->delete();
Flux::modal('delete-category')->close();
session()->flash('success', 'Category deleted successfully');
$this->redirectRoute('category.index', navigate: true);
}
};
?>
<div>
<flux:modal
name="edit-category"
class="md:w-150"
x-on:close="$wire.resetForm()"
>
<form class="space-y-8" wire:submit.prevent="updateCategory">
{{-- header --}}
<div class="space-y-2">
<flux:heading size="lg" class="text-zinc-900 dark:text-white">
Edit Category
</flux:heading>
<flux:text class="text-zinc-500 dark:text-zinc-400">
Edit your category details below
</flux:text>
</div>
{{-- form field --}}
<div class="space-y-6">
<flux:input
label="Name"
placeholder="Enter category name"
wire:model="form.name"
wire:dirty.class.text-red-500
/>
<flux:textarea
label="Description"
placeholder="Enter category description"
wire:model="form.description"
wire:dirty.class.text-red-500
/>
</div>
<div
wire:show ="$dirty"
class="text-red-500 dark:text-red-400"
>
you have unsaved changes
</div>
{{-- footer --}}
<div class="flex items-center justify-end gap-3 pt-4 border-t border-zinc-200 dark:border-zinc-800">
<flux:modal.close>
<flux:button variant="outline" color="neutral">Cancel</flux:button>
</flux:modal.close>
<flux:button variant="primary" color="primary" type="submit">Update</flux:button>
</div>
</form>
</flux:modal>
{{-- delete modal --}}
<flux:modal
name="delete-category"
class="md:w-150"
x-on:close="$wire.resetForm()"
>
<form class="space-y-8" wire:submit.prevent="deleteCategory">
{{-- header --}}
<div class="space-y-2">
<flux:heading size="lg" class="text-zinc-900 dark:text-white">
Delete Category
</flux:heading>
<flux:text class="text-zinc-500 dark:text-zinc-400">
this action cannot be undone
</flux:text>
</div>
{{-- footer --}}
<div class="flex items-center justify-end gap-3 pt-4 border-t border-zinc-200 dark:border-zinc-800">
<flux:modal.close>
<flux:button variant="outline" color="neutral">Cancel</flux:button>
</flux:modal.close>
<flux:button variant="primary" color="danger" type="submit">Delete</flux:button>
</div>
</form>
</flux:modal>
</div>
Langkah 8: Memperbarui Sidebar Navigasi
Agar pengguna dapat berpindah ke halaman Kategori dengan mudah, kita perlu mendaftarkannya pada menu sidebar navigasi utama aplikasi Anda (biasanya berada di layout utama seperti layouts.app atau komponen sidebar khusus).
Tambahkan baris berikut di dalam komponen sidebar Anda:
<flux:sidebar.item icon="folder"
:href="route('category.index')"
:current="request()->routeIs('category.index')"
wire:navigate
>
{{ __('Category') }}
</flux:sidebar.item>
Tips Tambahan: Membuat Komponen Notifikasi (Flash Message) Kustom
Jika Anda ingin notifikasi sukses yang tampil lebih modular di berbagai halaman, Anda bisa membuat komponen Blade kustom:
php artisan make:component FlashMessage --view
Buka file komponen di resources/views/components/flash-message.blade.php dan buatlah komponen notifikasi yang cantik:
@foreach (['success','error','warning','info'] as $type)
@php
$bgColor = match($type) {
'success' => 'bg-green-600 text-white',
'error' => 'bg-red-600 text-white',
'warning' => 'bg-yellow-600 text-white',
'info' => 'bg-blue-600 text-white',
};
@endphp
@if (session()->has($type))
<div
x-data="{ show: true }"
x-show="show"
x-init="setTimeout(() => show = false, 3000)"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform -translate-y-4"
x-transition:enter-end="opacity-100 transform translate-y-0"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0"
x-transition:leave-end="opacity-0 transform -translate-y-4"
class="{{ $bgColor }} fixed top-4 right-4 z-50 p-4 mb-4 text-sm rounded-lg" role="alert">
<span class="font-medium">{{ ucfirst($type) }}!</span> {{ session($type) }}
</div>
@endif
@endforeach
Sekarang, Anda cukup meletakkan <x-flash-message /> di layout utama aplikasi (app.blade.php), dan setiap notifikasi sukses yang dikirim lewat session()->flash('success') akan muncul secara elegan dengan animasi halus di pojok kanan bawah!
Ringkasan Alur Kerja
Dengan kombinasi ini, alur kerja pengembangan Anda menjadi sangat efisien:
- Model & Migration untuk mendefinisikan database schema.
- Form Object (
CategoryForm) untuk merapikan urusan validasi formulir dan binding data. - Livewire Component (
Index) sebagai pengendali logika halaman. - Flux UI Components untuk membangun antarmuka premium secara deklaratif tanpa pusing menulis CSS tambahan atau library JS eksternal.
Selamat mencoba membangun aplikasi hebat Anda berikutnya dengan Laravel, Livewire, dan Flux UI!