Documentation

Livewire Layout System

Livewire Layout System

Overview

UltraViolet's Livewire integration includes a dedicated layout system built specifically for reactive components. The layout handles assets, scripts, styles, and provides a clean structure for building dynamic interfaces.

Layout File Structure

resources/views/layouts/
├── admin-livewire.blade.php    # Main Livewire layout
└── partials/
    ├── header.blade.php         # Header section
    ├── plugins.blade.php        # Third-party scripts
    ├── cdn-scripts.blade.php    # CDN resources
    └── admin-action-bar.blade.php   # Action bar

Main Layout: admin-livewire.blade.php

Location: resources/views/layouts/admin-livewire.blade.php

This is the master layout for all Livewire pages. It provides:

  • ✅ Vite asset compilation
  • ✅ Livewire scripts and styles
  • ✅ @push/@stack support for component assets
  • ✅ Menu system integration
  • ✅ Breadcrumb support
  • ✅ Flash messages
  • ✅ Dark/light mode support

Basic Structure

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name') }}</title>

    <!-- Fonts -->
    <link rel="preconnect" href="https://fonts.bunny.net">

    <!-- Styles -->
    @vite(['resources/sass/admin-vertical.scss', 'resources/js/admin.ts'])

    <!-- Component Styles -->
    @stack('styles')
</head>
<body>
    <!-- Navigation -->
    @include('components.navigation.hui-vertical-menu')

    <!-- Page Content -->
    <div class="page-content">
        @yield('content')
    </div>

    <!-- Scripts -->
    @stack('scripts')
</body>
</html>

Vite Integration

UltraViolet uses Vite for asset compilation, providing lightning-fast builds and hot module replacement (HMR).

Configuration

vite.config.js:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/sass/admin-vertical.scss',
                'resources/js/admin.ts',
            ],
            refresh: true,
        }),
    ],
});

In the Layout

@vite(['resources/sass/admin-vertical.scss', 'resources/js/admin.ts'])

This compiles and includes:

  • SCSS → Compiled CSS with Bootstrap, UltraViolet theme, and component styles
  • TypeScript/JS → Admin functionality, Livewire integration, third-party libs

Development vs Production

Development (with HMR):

npm run dev

Production (minified):

npm run build

Vite automatically handles:

  • Asset hashing for cache busting
  • Code splitting
  • Tree shaking
  • Minification

@push and @stack Directives

UltraViolet's layout uses Laravel's @push and @stack to manage component-specific assets.

Stacks Available

1. @stack('styles')

For component-specific CSS

2. @stack('scripts')

For component-specific JavaScript

How It Works

In Layout (admin-livewire.blade.php):

<head>
    @vite(['resources/sass/admin-vertical.scss'])
    @stack('styles')  <!-- Component styles pushed here -->
</head>
<body>
    @yield('content')

    @stack('scripts')  <!-- Component scripts pushed here -->
</body>

In Component View:

@extends('layouts.admin-livewire')

@section('content')
    <div>
        <!-- Your component content -->
    </div>
@endsection

@push('styles')
<style>
    .my-component {
        background: #f0f0f0;
    }
</style>
@endpush

@push('scripts')
<script>
    console.log('Component loaded');
</script>
@endpush

Real Example: Vector Maps

@extends('layouts.admin-livewire')

@section('content')
    <livewire:maps-vector />
@endsection

@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/jsvectormap@1.5.3/dist/js/jsvectormap.min.js"></script>
<script>
    // Initialize map
    const map = new jsVectorMap({
        selector: '#map',
        map: 'world'
    });

    // Listen for Livewire updates
    Livewire.on('map-style-changed', (style) => {
        map.setStyle(style);
    });
</script>
@endpush

@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsvectormap@1.5.3/dist/css/jsvectormap.min.css">
@endpush

Asset Loading Order

Understanding the asset loading order is crucial:

1. Head Section

<head>
    <!-- 1. Meta tags -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <!-- 2. Fonts (preconnect) -->
    <link rel="preconnect" href="https://fonts.bunny.net">

    <!-- 3. Vite CSS -->
    @vite(['resources/sass/admin-vertical.scss'])

    <!-- 4. Livewire Styles (auto-injected) -->
    @livewireStyles

    <!-- 5. Component-specific styles -->
    @stack('styles')
</head>

2. Body Section

<body>
    <!-- Content renders here -->
    @yield('content')

    <!-- 1. Vite JavaScript -->
    @vite(['resources/js/admin.ts'])

    <!-- 2. Livewire Scripts (auto-injected) -->
    @livewireScripts

    <!-- 3. Component-specific scripts -->
    @stack('scripts')
</body>

Note: Livewire automatically injects @livewireStyles and @livewireScripts if not manually added.


Working with JavaScript

Livewire + Alpine.js

Livewire works seamlessly with Alpine.js (included in UltraViolet):

<div x-data="{ open: false }">
    <button @click="open = !open">Toggle</button>

    <div x-show="open" x-transition>
        <livewire:my-component />
    </div>
</div>

Dispatching Events to JavaScript

From Livewire Component:

<?php
namespace App\Livewire;

use Livewire\Component;

class MapController extends Component
{
    public function changeStyle($style)
    {
        $this->dispatch('map-style-changed', style: $style);
    }
}

In Blade View:

@push('scripts')
<script>
    Livewire.on('map-style-changed', (event) => {
        console.log('New style:', event.style);
        updateMap(event.style);
    });
</script>
@endpush

JavaScript → Livewire

@push('scripts')
<script>
    // Call Livewire method from JavaScript
    function saveData() {
        @this.call('save', { data: 'value' });
    }

    // Update Livewire property
    function updateCount() {
        @this.set('count', 10);
    }

    // Get Livewire property
    function getCount() {
        const count = @this.get('count');
        console.log(count);
    }
</script>
@endpush

Component-Specific Styles

Method 1: Inline Styles

@push('styles')
<style>
    .stats-widget {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        border-radius: 12px;
        padding: 1.5rem;
    }
</style>
@endpush

Method 2: External Stylesheet

@push('styles')
<link rel="stylesheet" href="asset-css-components-stats-widget.css.html">
@endpush

Method 3: Vite Import

In resources/js/admin.ts:

// Import component-specific styles
import './components/stats-widget.scss';

Best Practice: Use Vite imports for reusable component styles.


Third-Party Libraries

CDN Resources

For quick prototyping, use CDN (via @push):

@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/library@1.0.0/dist/library.min.css">
@endpush

@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/library@1.0.0/dist/library.min.js"></script>
@endpush

NPM Packages (Recommended)

For production, install via npm:

npm install library-name

Import in resources/js/admin.ts:

import LibraryName from 'library-name';

window.LibraryName = LibraryName;

Use in components:

@push('scripts')
<script>
    const instance = new LibraryName({
        // config
    });
</script>
@endpush

Menu Integration

The Livewire layout integrates with UltraViolet's dynamic menu system.

Menu Configuration

config/livewire-menus.php:

<?php

return [
    'admin' => [
        [
            'label' => 'Livewire Dash',
            'route' => 'livewire.dashboard',
            'icon' => 'flash_on',
            'type' => 'simple',
        ],
        [
            'label' => 'Live Widgets',
            'route' => '#',
            'icon' => 'extension',
            'type' => 'accordion',
            'children' => [
                [
                    'label' => 'Counter',
                    'route' => 'livewire.components.counter',
                    'icon' => 'add_circle',
                ],
                // More items...
            ],
        ],
    ],
];

Rendering the Menu

In Layout:

@include('components.navigation.hui-vertical-menu', [
    'menuConfig' => config('livewire-menus.admin')
])

Breadcrumbs

Defining Breadcrumbs

@extends('layouts.admin-livewire')

@section('breadcrumb-title', 'Livewire Dashboard')

@section('breadcrumbs')
    <li class="breadcrumb-item"><a href="../uv-vertical/index.html">Home</a></li>
    <li class="breadcrumb-item active">Livewire</li>
@endsection

@section('content')
    <!-- Your content -->
@endsection

Custom Breadcrumb Component

<x-chevron-breadcrumb 
    :items="[
        ['label' => 'Home', 'url' => '/'],
        ['label' => 'Livewire', 'url' => '/livewire/dashboard'],
        ['label' => 'Components', 'active' => true]
    ]" 
/>

Page Actions

Add action buttons to the page header:

@section('page-actions')
    <div class="d-flex gap-2">
        <button class="btn btn-primary" wire:click="save">
            <i class="material-symbols-rounded">save</i>
            Save
        </button>
        <button class="btn btn-outline-secondary" wire:click="cancel">
            Cancel
        </button>
    </div>
@endsection

Flash Messages

Display success/error messages:

// In Component
public function save()
{
    // Save logic...

    session()->flash('message', 'Successfully saved!');
    session()->flash('type', 'success'); // success, error, warning, info
}
@if (session()->has('message'))
    <div class="alert alert-{{ session('type', 'info') }} alert-dismissible fade show">
        {{ session('message') }}
        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
    </div>
@endif

Dark Mode Support

The layout automatically supports dark mode switching:

<body data-theme="{{ session('theme', 'dark') }}">

Toggle Dark Mode

<button onclick="toggleTheme()">
    Toggle Theme
</button>

@push('scripts')
<script>
    function toggleTheme() {
        const body = document.body;
        const current = body.dataset.theme;
        const newTheme = current === 'dark' ? 'light' : 'dark';

        body.dataset.theme = newTheme;

        // Save preference
        fetch('/api/preferences/theme', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
            },
            body: JSON.stringify({ theme: newTheme })
        });
    }
</script>
@endpush

Loading States

Show loading indicators during Livewire requests:

<div>
    <button wire:click="save">
        <span wire:loading.remove wire:target="save">Save</span>
        <span wire:loading wire:target="save">
            <i class="spinner-border spinner-border-sm"></i>
            Saving...
        </span>
    </button>
</div>

Global Loading Indicator

<div 
    wire:loading 
    class="position-fixed top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
    style="background: rgba(0,0,0,0.5); z-index: 9999;"
>
    <div class="spinner-border text-light" role="status">
        <span class="visually-hidden">Loading...</span>
    </div>
</div>

Performance Optimization

1. Lazy Loading Components

<livewire:stats-widget lazy />

Component loads after initial page render.

2. Asset Preloading

<link rel="preload" href="asset-js-heavy-library.js.html" as="script">

3. Defer Non-Critical Scripts

@push('scripts')
<script src="analytics.js" defer></script>
@endpush

4. Inline Critical CSS

<style>
    /* Critical CSS for above-the-fold content */
    .hero { /* ... */ }
</style>

Complete Example

Here's a complete Livewire page with all features:

@extends('layouts.admin-livewire')

@section('breadcrumb-title', 'User Dashboard')

@section('breadcrumbs')
    <li class="breadcrumb-item"><a href="../uv-vertical/index.html">Home</a></li>
    <li class="breadcrumb-item active">Dashboard</li>
@endsection

@section('page-actions')
    <button class="btn btn-primary" wire:click="refresh">
        <i class="material-symbols-rounded">refresh</i>
        Refresh
    </button>
@endsection

@section('content')
    <div class="row">
        <div class="col-md-4">
            <livewire:stats-widget lazy />
        </div>
        <div class="col-md-4">
            <livewire:mini-counter />
        </div>
        <div class="col-md-4">
            <livewire:quick-todo />
        </div>
    </div>
@endsection

@push('styles')
<style>
    .dashboard-card {
        border-radius: 12px;
        transition: transform 0.2s;
    }
    .dashboard-card:hover {
        transform: translateY(-4px);
    }
</style>
@endpush

@push('scripts')
<script>
    // Initialize tooltips
    const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
    tooltips.forEach(el => new bootstrap.Tooltip(el));

    // Listen for Livewire events
    Livewire.on('stats-updated', (data) => {
        console.log('Stats updated:', data);
    });
</script>
@endpush

Troubleshooting

Assets Not Loading

Problem: CSS/JS not loading in development

Solution:

# Make sure Vite dev server is running
npm run dev

Livewire Not Working

Problem: Livewire directives not responding

Solution: Check that Livewire scripts are loaded:

@livewireScripts  <!-- Add if missing -->

Script Execution Order

Problem: Scripts running before DOM ready

Solution: Use DOMContentLoaded:

document.addEventListener('DOMContentLoaded', function() {
    // Your code here
});

Next Steps

  • [Components Guide]({{ route('docs.show', 'livewire/components') }}) - Explore all 9 components
  • [Best Practices]({{ route('docs.show', 'livewire/best-practices') }}) - Tips and patterns
  • Vite Documentation - Learn more about Vite

The layout is your foundation. Master it, and building Livewire components becomes effortless! 🚀