Lección 14 de 45 12 min de lectura

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:

blade
{{-- 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:

blade
{{-- 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:

blade
<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():

blade
{{-- 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:

blade
{{-- 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:

blade
{{-- 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:

blade
{{-- 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:

blade
{{-- 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():

blade
{{-- 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:

blade
{{-- components/input.blade.php --}}
@props(['disabled' => false])

<input {{ $attributes }} @disabled($disabled)>

{{-- Uso --}}
<x-input name="email" disabled />
Directivas de atributos

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:

bash
php artisan make:component Alert

Esto crea dos archivos:

php
// 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');
    }
}
blade
{{-- 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

  • $slot contiene el contenido por defecto del componente
  • <x-slot name="..."> define slots con nombre
  • @props declara los atributos del componente con valores por defecto
  • $attributes contiene 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

¿Te está gustando el curso?

Tenemos cursos premium con proyectos reales, soporte personalizado y certificado.

Descubrir cursos premium