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:
@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:
<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:
<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:
{{-- 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:
<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:
<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:
{{-- 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:
{{-- resources/views/components/meta.blade.php --}}
@foreach ($attributes->whereStartsWith('data-') as $nama => $nilai)
<meta property="{{ $nama }}" content="{{ $nilai }}">
@endforeachTag <x-meta data-halaman="tugas" data-pengguna="budi" /> menghasilkan dua elemen:
<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.