Skip to main content

Overview

LaraCMS uses Laravel Livewire for reactive, dynamic components without writing JavaScript. Components are located in app/Livewire/ and follow a structured namespace organization.

Configuration

Livewire configuration is defined in config/livewire.php:
return [
    'class_namespace' => 'App\\Livewire',
    'class_path' => app_path('Livewire'),
    'view_path' => resource_path('views/livewire'),
    'component_layout' => 'layouts::app',
    'pagination_theme' => 'tailwind',
];

Key Configuration Options

  • Component Locations: Views are stored in resources/views/livewire/
  • Class Namespace: All components use App\Livewire namespace
  • Pagination: Uses Tailwind CSS theme by default
  • Layout: Default layout is layouts::app

Component Structure

Livewire components in LaraCMS are organized by feature:
app/Livewire/
├── Actions/
│   └── Logout.php
├── Admin/
│   ├── BlogEdit.php
│   ├── BlogList.php
│   ├── UserList.php
│   ├── CreateUser.php
│   ├── EditUser.php
│   ├── Gallery/
│   │   ├── Managealbum.php
│   │   └── Managephoto.php
│   └── Settings/
│       └── SettingsPage.php
└── Auth/
    ├── Login.php
    ├── Register.php
    ├── ForgotPassword.php
    └── ResetPassword.php

Component Examples

Basic Component

The simplest component structure (app/Livewire/Admin/BlogList.php):
namespace App\Livewire\Admin;

use Livewire\Component;

class BlogList extends Component
{
    public function render()
    {
        return view('livewire.admin.blog-list');
    }
}

Component with Data Binding

The BlogEdit component passes data to its view:
namespace App\Livewire\Admin;

use App\Models\Post;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class BlogEdit extends Component
{
    public Post $post;

    public function mount($id)
    {
        $this->post = Post::findOrFail($id);
    }

    public function render()
    {
        return view('livewire.admin.blog-edit', [
            'posts' => Auth::user()->posts,
        ]);
    }
}

Advanced Component Patterns

Pagination Component

The UserList component demonstrates pagination, search, and sorting (app/Livewire/Admin/UserList.php):
namespace App\Livewire\Admin;

use Livewire\Component;
use App\Models\User;
use Livewire\WithPagination;

class UserList extends Component
{
    use WithPagination;

    public $search = '';
    public $sortField = 'name';
    public $sortDirection = 'asc';
    public $showDeleteModal = false;
    public $userToDelete = null;

    protected $queryString = [
        'search' => ['except' => ''],
        'sortField' => ['except' => 'name'],
        'sortDirection' => ['except' => 'asc'],
    ];

    public function mount()
    {
        $this->resetPage();
    }

    public function sortBy($field)
    {
        if ($this->sortField === $field) {
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortField = $field;
            $this->sortDirection = 'asc';
        }
    }

    public function updatingSearch()
    {
        $this->resetPage();
    }

    public function confirmDelete($userId)
    {
        $this->userToDelete = $userId;
        $this->showDeleteModal = true;
    }

    public function deleteUser()
    {
        $user = User::findOrFail($this->userToDelete);
        $user->delete();

        $this->showDeleteModal = false;
        $this->userToDelete = null;

        session()->flash('message', 'User deleted successfully.');
    }

    public function render()
    {
        $users = User::query()
            ->with('roles')
            ->when($this->search, function ($query) {
                $query->where('name', 'like', '%' . $this->search . '%')
                    ->orWhere('email', 'like', '%' . $this->search . '%');
            })
            ->orderBy($this->sortField, $this->sortDirection)
            ->paginate(10)
            ->withQueryString();

        return view('livewire.admin.user-list', ['users' => $users]);
    }
}

Key Features:

  • WithPagination trait: Enables pagination
  • Query string binding: Preserves state in URL
  • Real-time search: Auto-resets pagination on search
  • Dynamic sorting: Toggle sort direction on column click
  • Modal dialogs: Confirmation before delete actions
  • Flash messages: User feedback after operations
The Managealbum component shows form handling and media uploads:
namespace App\Livewire\Admin\Gallery;

use Livewire\Component;
use App\Models\Album;
use Livewire\WithPagination;

class Managealbum extends Component
{
    use WithPagination;

    public $showAlbumModal = false;
    public $showDeleteModal = false;
    public $albumToDelete = null;

    // Album form properties
    public $album_name;
    public $description;
    public $category;
    public $album_cover;

    protected $rules = [
        'album_name' => 'required|min:3|max:255',
        'description' => 'nullable|max:1000',
        'category' => 'nullable|max:255',
        'album_cover' => 'nullable|image|max:1024'
    ];

    public function createAlbum()
    {
        $this->validate();

        $album = Album::create([
            'album_name' => $this->album_name,
            'description' => $this->description,
            'category' => $this->category,
        ]);

        if ($this->album_cover) {
            $album->addMedia($this->album_cover)
                ->toMediaCollection('album_cover');
        }

        $this->reset(['album_name', 'description', 'category', 'album_cover', 'showAlbumModal']);
        $this->dispatch('album-created');
    }

    public function confirmDelete($albumId)
    {
        $this->albumToDelete = $albumId;
        $this->showDeleteModal = true;
    }

    public function deleteAlbum()
    {
        if ($this->albumToDelete) {
            $album = Album::find($this->albumToDelete);

            if ($album) {
                $album->photos()->detach();
                $album->clearMediaCollection('album_cover');
                $album->delete();
            }
        }

        $this->reset(['albumToDelete', 'showDeleteModal']);
    }

    public function render()
    {
        $albums = Album::with('media')
            ->latest()
            ->paginate(12, ['*'], 'albumsPage');

        return view('livewire.admin.gallery.managealbum', ['albums' => $albums]);
    }
}

Key Features:

  • Form validation: Laravel validation rules
  • Media handling: Integration with Spatie Media Library
  • Event dispatching: Notify other components
  • State reset: Clear form after submission
  • Relationship management: Detach related models on delete

Authentication Component

The Login component uses Livewire attributes for validation:
namespace App\Livewire\Auth;

use Illuminate\Auth\Events\Lockout;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Validate;
use Livewire\Component;

#[Layout('components.layouts.auth')]
class Login extends Component
{
    #[Validate('required|string|email')]
    public string $email = '';

    #[Validate('required|string')]
    public string $password = '';

    public bool $remember = false;

    public function login(): void
    {
        $this->validate();

        $this->ensureIsNotRateLimited();

        if (! Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) {
            RateLimiter::hit($this->throttleKey());

            throw ValidationException::withMessages([
                'email' => __('auth.failed'),
            ]);
        }

        RateLimiter::clear($this->throttleKey());
        Session::regenerate();

        $this->redirectIntended(default: route('home', absolute: false), navigate: true);
    }

    protected function ensureIsNotRateLimited(): void
    {
        if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
            return;
        }

        event(new Lockout(request()));

        $seconds = RateLimiter::availableIn($this->throttleKey());

        throw ValidationException::withMessages([
            'email' => __('auth.throttle', [
                'seconds' => $seconds,
                'minutes' => ceil($seconds / 60),
            ]),
        ]);
    }

    protected function throttleKey(): string
    {
        return Str::transliterate(Str::lower($this->email) . '|' . request()->ip());
    }
}

Key Features:

  • Attribute validation: Modern PHP 8 attributes for validation
  • Rate limiting: Protect against brute force attacks
  • Custom layout: Uses #[Layout] attribute
  • Session management: Regenerate session on login
  • SPA navigation: wire:navigate for smooth transitions

Component Views

Corresponding Blade views use Livewire directives:
<div>
    <x-admin.alerts />

    <x-table>
        <x-slot name="advancedheader">
            <div class="flex flex-col md:flex-row items-center justify-between">
                <div class="w-full md:w-1/2">
                    <input type="text" 
                        wire:model.live.debounce="search"
                        placeholder="Search users...">
                </div>
                <flux:button href="{{ route('admin.users.create') }}" icon="user-plus">
                    Add User
                </flux:button>
            </div>
        </x-slot>

        <x-slot name="head">
            <x-table.heading 
                sortable 
                wire:click="sortBy('name')" 
                :direction="$sortField === 'name' ? $sortDirection : null">
                Username
            </x-table.heading>
        </x-slot>

        <x-slot name="body">
            @forelse ($users as $user)
                <x-table.row>
                    <x-table.cell>{{ $user->name }}</x-table.cell>
                    <x-table.cell>
                        <flux:button 
                            wire:click="confirmDelete({{ $user->id }})" 
                            variant="danger">
                            Delete
                        </flux:button>
                    </x-table.cell>
                </x-table.row>
            @empty
                <x-table.row>
                    <x-table.cell colspan="9">
                        No users found.
                    </x-table.cell>
                </x-table.row>
            @endforelse
        </x-slot>
    </x-table>

    {{ $users->links() }}

    <!-- Delete Modal -->
    <x-modal wire:model="showDeleteModal">
        <!-- Modal content -->
    </x-modal>
</div>

Livewire Directives

Data Binding

  • wire:model.live - Real-time updates
  • wire:model.live.debounce - Debounced updates
  • wire:model.defer - Update on form submit

Actions

  • wire:click - Trigger component methods
  • wire:submit - Handle form submission
  • wire:confirm - Confirmation dialog
  • wire:navigate - SPA-like navigation

Creating Custom Components

Generate a Component

php artisan make:livewire Admin/CustomComponent
This creates:
  • app/Livewire/Admin/CustomComponent.php
  • resources/views/livewire/admin/custom-component.blade.php

Component Template

namespace App\Livewire\Admin;

use Livewire\Component;

class CustomComponent extends Component
{
    public $property = '';

    protected $rules = [
        'property' => 'required|min:3',
    ];

    public function save()
    {
        $this->validate();
        
        // Save logic
        
        session()->flash('message', 'Saved successfully.');
    }

    public function render()
    {
        return view('livewire.admin.custom-component');
    }
}

Best Practices

1. Use Query String for State

Preserve component state in the URL:
protected $queryString = [
    'search' => ['except' => ''],
    'page' => ['except' => 1],
];

2. Reset Pagination on Filter Changes

public function updatingSearch()
{
    $this->resetPage();
}

3. Use Events for Component Communication

// Dispatch event
$this->dispatch('user-created');

// Listen for event
protected $listeners = ['user-created' => 'refreshList'];

4. Optimize Queries

Eager load relationships to prevent N+1 queries:
$users = User::with('roles', 'media')->paginate(10);

5. Validate User Input

Always validate data before processing:
protected $rules = [
    'email' => 'required|email|unique:users',
    'name' => 'required|min:3|max:255',
];

Performance Optimization

Lazy Loading

use Livewire\Attributes\Lazy;

#[Lazy]
class HeavyComponent extends Component
{
    // Component logic
}

Payload Limits

Configured in config/livewire.php:
'payload' => [
    'max_size' => 1024 * 1024,   // 1MB
    'max_nesting_depth' => 10,
    'max_calls' => 50,
    'max_components' => 20,
],

Next Steps

Build docs developers (and LLMs) love