Slots y atributos
Los slots permiten pasar contenido HTML a los componentes de forma flexible. Los atributos te dan control total sobre las propiedades y clases CSS que recibe un componente.
El slot por defecto
Ya vimos que el contenido entre las etiquetas de un componente se pasa como $slot:
{{-- components/card.blade.php --}}
<div class="card">
{{ $slot }}
</div>
{{-- Uso --}}
<x-card>
<p>Este contenido va dentro del slot</p>
</x-card>
Slots con nombre
Cuando necesitas múltiples áreas de contenido, usa slots con nombre:
{{-- components/card.blade.php --}}
<div class="card">
<div class="card-header">
{{ $header }}
</div>
<div class="card-body">
{{ $slot }}
</div>
<div class="card-footer">
{{ $footer }}
</div>
</div>
Usa la directiva x-slot para definir el contenido de cada slot:
<x-card>
<x-slot name="header">
<h2>Título de la tarjeta</h2>
</x-slot>
<p>Contenido principal (slot por defecto)</p>
<x-slot name="footer">
<button>Acción</button>
</x-slot>
</x-card>
Slots opcionales
Puedes verificar si un slot tiene contenido con isset() o $slot->isEmpty():
{{-- components/card.blade.php --}}
<div class="card">
@isset($header)
<div class="card-header">
{{ $header }}
</div>
@endisset
<div class="card-body">
{{ $slot }}
</div>
@if(!$footer->isEmpty())
<div class="card-footer">
{{ $footer }}
</div>
@endif
</div>
Atributos de componentes
Los atributos que pasas al componente están disponibles como variables. Puedes definir valores por defecto con @props:
{{-- components/button.blade.php --}}
@props(['type' => 'button', 'color' => 'primary'])
<button type="{{ $type }}" class="btn btn-{{ $color }}">
{{ $slot }}
</button>
{{-- Uso --}}
<x-button>Guardar</x-button>
<x-button type="submit" color="success">Enviar</x-button>
<x-button color="danger">Eliminar</x-button>
El objeto $attributes
$attributes contiene todos los atributos que no están definidos en @props. Es muy útil para pasar atributos HTML adicionales:
{{-- components/input.blade.php --}}
@props(['label', 'name'])
<div class="form-group">
<label for="{{ $name }}">{{ $label }}</label>
<input id="{{ $name }}" name="{{ $name }}" {{ $attributes }}>
</div>
{{-- Uso --}}
<x-input
label="Email"
name="email"
type="email"
placeholder="tu@email.com"
required
/>
Los atributos type, placeholder y required se pasan automáticamente al <input>.
Merge de clases CSS
$attributes->merge() combina clases CSS del componente con las que pasa el usuario:
{{-- components/button.blade.php --}}
<button {{ $attributes->merge(['class' => 'btn btn-primary']) }}>
{{ $slot }}
</button>
{{-- Uso --}}
<x-button class="mt-4">Guardar</x-button>
{{-- Resultado: class="btn btn-primary mt-4" --}}
Clases condicionales
Usa $attributes->class() para añadir clases de forma condicional:
{{-- components/alert.blade.php --}}
@props(['type' => 'info', 'dismissible' => false])
<div {{ $attributes->class([
'alert',
'alert-' . $type,
'alert-dismissible' => $dismissible
]) }}>
{{ $slot }}
@if($dismissible)
<button type="button" class="btn-close"></button>
@endif
</div>
{{-- Uso --}}
<x-alert type="success" dismissible>
Operación completada
</x-alert>
Filtrar atributos
Puedes filtrar qué atributos se pasan con only(), except() y filter():
{{-- Solo pasar ciertos atributos --}}
<input {{ $attributes->only(['type', 'name', 'value']) }}>
{{-- Excluir atributos --}}
<div {{ $attributes->except(['class']) }}>
{{-- Filtrar con callback --}}
<input {{ $attributes->filter(fn ($value, $key) => $key !== 'class') }}>
Atributos booleanos
Los atributos sin valor se tratan como booleanos:
{{-- components/input.blade.php --}}
@props(['disabled' => false])
<input {{ $attributes }} @disabled($disabled)>
{{-- Uso --}}
<x-input name="email" disabled />
Laravel incluye directivas como @disabled, @checked, @selected y @required para manejar atributos booleanos de forma limpia.
Componentes de clase
Para componentes con lógica compleja, crea una clase PHP:
php artisan make:component Alert
Esto crea dos archivos:
// app/View/Components/Alert.php
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
public function __construct(
public string $type = 'info',
public string $message = ''
) {}
public function alertClass(): string
{
return match($this->type) {
'success' => 'bg-green-100 text-green-800',
'error' => 'bg-red-100 text-red-800',
'warning' => 'bg-yellow-100 text-yellow-800',
default => 'bg-blue-100 text-blue-800',
};
}
public function render()
{
return view('components.alert');
}
}
{{-- resources/views/components/alert.blade.php --}}
<div {{ $attributes->merge(['class' => $alertClass()]) }}>
{{ $message ?: $slot }}
</div>
{{-- Uso --}}
<x-alert type="success" message="Guardado correctamente" />
Ejercicios
Ejercicio 1: Card con slots
Crea un componente card con slots opcionales para header, footer e image.
Ver solución
{{-- components/card.blade.php --}}
<div {{ $attributes->merge(['class' => 'card']) }}>
@isset($image)
<div class="card-image">
{{ $image }}
</div>
@endisset
@isset($header)
<div class="card-header">
{{ $header }}
</div>
@endisset
<div class="card-body">
{{ $slot }}
</div>
@isset($footer)
<div class="card-footer">
{{ $footer }}
</div>
@endisset
</div>
{{-- Uso --}}
<x-card class="shadow-lg">
<x-slot name="image">
<img src="/foto.jpg" alt="Foto">
</x-slot>
<x-slot name="header">
<h3>Título</h3>
</x-slot>
<p>Contenido de la tarjeta</p>
<x-slot name="footer">
<x-button>Ver más</x-button>
</x-slot>
</x-card>
Ejercicio 2: Input con $attributes
Crea un componente form-input que reciba label, name y error, y pase el resto de atributos al input.
Ver solución
{{-- components/form-input.blade.php --}}
@props(['label', 'name', 'error' => null])
<div class="form-group">
<label for="{{ $name }}">{{ $label }}</label>
<input
id="{{ $name }}"
name="{{ $name }}"
{{ $attributes->merge(['class' => 'form-control' . ($error ? ' is-invalid' : '')]) }}
>
@if($error)
<span class="error-message">{{ $error }}</span>
@endif
</div>
{{-- Uso --}}
<x-form-input
label="Email"
name="email"
type="email"
:error="$errors->first('email')"
placeholder="tu@email.com"
required
/>
Ejercicio 3: Botón con variantes
Crea un componente button que acepte variant (primary, secondary, danger) y size (sm, md, lg) con merge de clases.
Ver solución
{{-- components/button.blade.php --}}
@props([
'variant' => 'primary',
'size' => 'md',
'type' => 'button'
])
@php
$variantClasses = [
'primary' => 'bg-blue-600 text-white hover:bg-blue-700',
'secondary' => 'bg-gray-200 text-gray-800 hover:bg-gray-300',
'danger' => 'bg-red-600 text-white hover:bg-red-700',
];
$sizeClasses = [
'sm' => 'px-2 py-1 text-sm',
'md' => 'px-4 py-2',
'lg' => 'px-6 py-3 text-lg',
];
@endphp
<button
type="{{ $type }}"
{{ $attributes->class([
'rounded font-medium transition',
$variantClasses[$variant] ?? $variantClasses['primary'],
$sizeClasses[$size] ?? $sizeClasses['md'],
]) }}
>
{{ $slot }}
</button>
{{-- Uso --}}
<x-button>Normal</x-button>
<x-button variant="danger" size="lg">Eliminar</x-button>
<x-button variant="secondary" size="sm" class="ml-2">Cancelar</x-button>
Resumen
-
$slotcontiene el contenido por defecto del componente -
<x-slot name="...">define slots con nombre -
@propsdeclara los atributos del componente con valores por defecto -
$attributescontiene los atributos no declarados en @props -
$attributes->merge()combina clases CSS -
$attributes->class()añade clases condicionales - Componentes de clase permiten lógica PHP compleja
¿Has encontrado un error o tienes una sugerencia para mejorar esta lección?
Escríbenos¿Te está gustando el curso?
Tenemos cursos premium con proyectos reales, soporte personalizado y certificado.
Descubrir cursos premium