@include memang bisa membagikan potongan tampilan, tetapi API-nya longgar: data dikirim lewat array, tidak ada deklarasi properti, dan nama variabel bisa bertabrakan tanpa terdeteksi. Component memperbaiki semuanya — potongan tampilan mendapat tag HTML sendiri seperti <x-alert>, properti yang jelas, serta tempat logika bila dibutuhkan. Laravel menyediakan dua rasa: anonymous component yang murni berkas Blade, dan class-based component dengan class PHP penuh. Kita bangun keduanya di chapter ini, lalu merendernya lewat HTTP untuk melihat HTML akhirnya; konsep ini adalah dasar dari component Blade modern.

Anonymous Component: x-alert #

Anonymous component hanyalah berkas Blade di folder khusus resources/views/components/. Nama berkas menjadi nama tag: alert.blade.php otomatis tersedia sebagai <x-alert> di view mana pun. Buat berkasnya:

blade
{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info', 'title' => null])

<div class="alert alert-{{ $type }}">
    @if ($title)
        <strong>{{ $title }}:</strong>
    @endif
    {{ $slot }}
</div>

Directive @props mendeklarasikan properti component — daftar nama yang boleh diterima selain atribut biasa. Nilai setelah => adalah default: type ber-fallback info bila tidak dikirim, dan title bernilai null sehingga blok @if di bawahnya otomatis melewati heading. Isi di antara tag pembuka dan penutup <x-alert> tersedia sebagai variabel $slot — kita bedah tuntas di chapter Slots dan Attributes.

Prop Statis dan Dinamis #

Component dipanggil seperti tag HTML biasa. Atribut statis dikirim sebagai string; atribut berawalan titik dua adalah ekspresi PHP yang dievaluasi lebih dulu. Halaman resources/views/contoh-component.blade.php memakai keduanya:

blade
<h2>Pengaturan Akun</h2>

<x-alert type="success" title="Berhasil">
    Profil Anda sudah diperbarui.
</x-alert>

<x-alert :type="$status">
    Perubahan baru aktif setelah sesi berikutnya.
</x-alert>

Route mengirim ['status' => 'warning'], sehingga :type="$status" menghasilkan prop $type = 'warning'. Halaman dirender lewat route biasa dan diambil dengan GET /pengaturan; Output yang Dihasilkan:

text
<div class="alert alert-success">
            <strong>Berhasil:</strong>
        Profil Anda sudah diperbarui.
</div>

<div class="alert alert-warning">
        Perubahan baru aktif setelah sesi berikutnya.
</div>

Tanpa atribut apa pun, default dari @props bekerja — <x-alert>Tanpa prop.</x-alert> menghasilkan class="alert alert-info" karena prop $type jatuh ke nilai bawaannya.

Class-based Component: x-button #

Saat component butuh logika lebih dari sekadar default, pindahkan kode ke class PHP. Perintah make:component menyiapkan pasangan class dan view:

text
$ php artisan make:component Button

   INFO  Component [app/View/Components/Button.php] created successfully.

   INFO  View [resources/views/components/button.blade.php] created successfully.

Properti dideklarasikan lewat constructor — dengan type declaration penuh karena ini class PHP biasa. Method pada class juga bisa dipanggil langsung dari view-nya. Isi app/View/Components/Button.php:

php
<?php

namespace App\View\Components;

use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;

class Button extends Component
{
    public function __construct(
        public string $label = 'Simpan',
        public string $variant = 'primary',
    ) {}

    public function variantClass(): string
    {
        return match ($this->variant) {
            'primary' => 'btn btn-primary',
            'secondary' => 'btn btn-outline',
            'danger' => 'btn btn-danger',
            default => 'btn',
        };
    }

    public function render(): View|Closure|string
    {
        return view('components.button');
    }
}

Sedangkan resources/views/components/button.blade.php tinggal memakai properti dan method tersebut:

blade
<button type="button" class="{{ $variantClass() }}">{{ $label }}</button>

Pemetaan match pada variantClass() menunjukkan letak kekuatan class-based component: logika non-trivial — pemetaan variant, perhitungan tanggal, query ringan — hidup di class yang bisa diuji terpisah, bukan tercecer di template.

Merender Semuanya dalam Satu Halaman #

Kedua jenis component hidup berdampingan di satu halaman yang sama:

blade
<x-button label="Simpan Profil" />
<x-button label="Hapus Akun" variant="danger" />

Output yang Dihasilkan untuk GET /pengaturan (heading dan kedua alert memakai contoh sebelumnya):

text
<button type="button" class="btn btn-primary">Simpan Profil</button>
<button type="button" class="btn btn-danger">Hapus Akun</button>

Tag <x-button /> yang ditulis menutup sendiri menandakan component tanpa slot; bila ada konten, tag pembuka dan penutup wajib berpasangan. Nama prop yang terdiri dari dua kata pun punya kesepakatan sendiri: di tag ditulis kebab-case, di @props dan constructor ditulis camelCase. Prop nilaiDefault misalnya dikirim lewat atribut nilai-default="Nilai awal" — kita verifikasi pemetaan ini menghasilkan Nilai awal tepat di dalam prop, sementara tag tanpa atribut tersebut jatuh kembali ke nilai default-nya. Pilihan anonymous atau class-based pun fleksibel: mulai dari anonymous, lalu angkat ke class saat logikanya tumbuh — perintah make:component Alert --view bahkan bisa membuat anonymous component dari terminal.

Tag, properti, dan isi sudah berjalan. Yang belum kita sentuh adalah dua objek istimewa di baliknya: $slot penampung konten dan $attributes penampung atribut — keduanya menjadi fokus chapter berikutnya.