Chapter sebelumnya menunjukkan component yang dikonfigurasi lewat properti — tetapi properti tidak bisa membawa potongan HTML besar, dan tidak semua atribut bisa kita daftarkan satu per satu di @props. Blade menutup dua celah itu dengan dua objek otomatis: $slot menampung konten yang ditulis di antara tag component, sedangkan $attributes menampung semua atribut tambahan yang tidak dikonsumsi properti. Keduanya bekerja seperti tangan kanan-kiri component — konten dari satu sisi, atribut dari sisi lain. Chapter ini menguji keduanya pada component kartu dan tombol sungguhan hingga HTML finalnya.

Slot Utama dengan $slot #

Isi yang kita tulis di antara tag pembuka dan penutup component otomatis tersedia sebagai variabel $slot di dalam berkas componentnya. Tidak perlu deklarasi apa pun — cukup cetak {{ $slot }} di titik yang tepat. Slot ini bisa berisi teks, elemen HTML, bahkan hasil directive Blade apa pun karena seluruhnya sudah dievaluasi sebelum dikirim.

Named Slot dengan x-slot #

Satu $slot tidak cukup untuk component berstruktur — kartu dengan header, isi, dan footer butuh tiga titik tujuan. Di sinilah named slot masuk: pemanggil menandai konten dengan <x-slot:nama>, dan component menerima variabel $nama bertipe objek slot. Berkas resources/views/components/card.blade.php:

blade
@props(['heading' => null])

<div class="card">
    @isset($heading)
        <div class="card-header">{{ $heading }}</div>
    @endisset

    <div class="card-body">
        {{ $slot }}
    </div>

    @isset($footer)
        <div class="card-footer">{{ $footer }}</div>
    @endisset
</div>

Cara memakainya dari halaman — konten tanpa tag slot jatuh ke $slot, sisanya masuk slot yang disebut:

blade
<x-card>
    <x-slot:heading>Ringkasan Tugas</x-slot>
    <p>Selesai 4 dari 6 tugas pekan ini.</p>
    <x-slot:footer>
        <small>Diperbarui hari ini oleh Budi.</small>
    </x-slot>
</x-card>

Output yang Dihasilkan:

text
<div class="card">
            <div class="card-header">Ringkasan Tugas</div>

    <div class="card-body">
        <p>Selesai 4 dari 6 tugas pekan ini.</p>
    </div>

            <div class="card-footer"><small>Diperbarui hari ini oleh Budi.</small></div>
    </div>

Directive @isset di component bukan hiasan — named slot yang tidak dikirim tidak menjadi variabel null, melainkan tidak ada sama sekali. Kartu yang dipanggil tanpa <x-slot:heading> dan <x-slot:footer> benar-benar kehilangan kedua div pembungkusnya.

Menggabungkan Class dengan $attributes->merge #

Method merge() pada $attributes menyatukan atribut default component dengan atribut dari pemanggil. Kasus paling umum: atribut class — component punya class dasarnya, pemanggil menambah class sendiri. Kita ubah tampilan <x-button> dari chapter sebelumnya menjadi:

blade
{{-- resources/views/components/button.blade.php --}}
@props(['type' => 'button'])

<button {{ $attributes->merge(['type' => $type, 'class' => $variantClass()]) }}>
    @if ($slot->isEmpty())
        {{ $label }}
    @else
        {{ $slot }}
    @endif
</button>

Array pertama merge() adalah default: type berasal dari prop, class dari method variantClass() milik class component. Pemanggilan berikut menguji tiga hal sekaligus — penggabungan class, penerusan atribut bebas, dan fallback saat slot kosong:

blade
<x-button label="Simpan" class="w-full" data-tekan="ya" />
<x-button variant="danger" onclick="return confirm('Yakin?')">Hapus Akun</x-button>

Output yang Dihasilkan:

text
<button type="button" class="btn btn-primary w-full" data-tekan="ya">
            Simpan
    </button>
<button type="button" class="btn btn-danger" onclick="return confirm('Yakin?')">
            Hapus Akun
    </button>

Perhatikan class="btn btn-primary w-full" — class dasar dari component muncul dulu, lalu class milik pemanggil disambung di belakangnya. Atribut lain seperti data-tekan dan onclick diteruskan apa adanya karena merge() hanya menggabungkan key yang bentrok. Tombol kedua membuktikan fallback: slot berisi teks, jadi $label diabaikan dan isi slot yang dipakai.

Menyaring Atribut dengan only dan except #

Kadang component hanya boleh meneruskan sebagian atribut — misalnya menyembunyikan atribut debug dari HTML akhir. Bag $attributes punya penyaringnya: only() mengambil yang disebut saja, except() membuang yang disebut:

blade
{{-- resources/views/components/lencana.blade.php --}}
@props(['label' => null])

<span {{ $attributes->only(['class']) }}>{{ $label ?? $slot }}</span>

Panggilan <x-lencana class="badge penting" label="Segera" id="lencana-1" /> menghasilkan <span class="badge penting">Segera</span> — atribut id dibuang karena tidak lolos saringan only(). Sebaliknya <section {{ $attributes->except(['data-debug']) }}> pada component panel membuang data-debug saja, sisanya diteruskan.

Bag atribut juga bisa diiterasi. Method whereStartsWith() menyaring atribut berdasarkan awalan nama — berguna untuk meneruskan seluruh atribut data-* sekaligus:

blade
{{-- resources/views/components/meta.blade.php --}}
@foreach ($attributes->whereStartsWith('data-') as $nama => $nilai)
    <meta property="{{ $nama }}" content="{{ $nilai }}">
@endforeach

Tag <x-meta data-halaman="tugas" data-pengguna="budi" /> menghasilkan dua elemen:

text
<meta property="data-halaman" content="tugas">
    <meta property="data-pengguna" content="budi">

Memeriksa Slot Kosong #

Method isEmpty() pada objek slot menentukan render kondisional. Tombol di atas memakainya: slot kosong berarti pakai $label, slot terisi berarti pakai isi slotnya. Pasangannya, isNotEmpty(), berguna untuk format sebaliknya seperti @if ($slot->isNotEmpty()).

Slot dan attribute bag menuntaskan anatomi component. Dengan bekal ini kita siap menyusun halaman yang benar-benar berinteraksi dengan pengguna — form dengan @csrf, pengisian ulang old(), dan pesan validasi — materi chapter berikutnya.