Output:
Chunk ID: 7f2ef0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2281
Output:
<?php

namespace App\Livewire\Admin;

use App\Enums\FinancialStatus;
use App\Models\Cart;
use App\Models\Checkout;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Store;
use App\Support\Money;
use Carbon\CarbonInterface;
use Carbon\CarbonPeriod;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked;
use Livewire\Component;
use Throwable;

class Dashboard extends Component
{
    #[Locked]
    public int $storeId;

    public string $storeCurrency = 'EUR';

    public string $dateRange = 'last_30_days';

    public ?string $customStartDate = null;

    public ?string $customEndDate = null;

    public int $totalSales = 0;

    public int $ordersCount = 0;

    public int $averageOrderValue = 0;

    public int $visitorsCount = 0;

    public float $salesChange = 0.0;

    public float $ordersChange = 0.0;

    public float $aovChange = 0.0;

    public float $visitorsChange = 0.0;

    /**
     * @var list<array{date: string, label: string, count: int}>
     */
    public array $ordersChartData = [];

    public int $maxOrdersChartCount = 1;

    /**
     * @var list<array{title: string, units_sold: int, revenue: int}>
     */
    public array $topProducts = [];

    /**
     * @var array{visits: int, add_to_cart: int, checkout_started: int, checkout_completed: int}
     */
    public array $funnelData = [
        'visits' => 0,
        'add_to_cart' => 0,
        'checkout_started' => 0,
        'checkout_completed' => 0,
    ];

    public function mount(): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->storeId = $store->getKey();
        $this->storeCurrency = $store->default_currency;

        $this->loadKpis();
    }

    public function updatedDateRange(): void
    {
        $this->loadKpis();
    }

    public function updatedCustomStartDate(): void
    {
        if ($this->dateRange === 'custom') {
            $this->loadKpis();
        }
    }

    public function updatedCustomEndDate(): void
    {
        if ($this->dateRange === 'custom') {
            $this->loadKpis();
        }
    }

    public function loadKpis(): void
    {
        [$start, $end] = $this->currentRange();
        [$previousStart, $previousEnd] = $this->previousRange($start, $end);

        $this->ordersCount = $this->ordersBetween($start, $end)->count();
        $previousOrdersCount = $this->ordersBetween($previousStart, $previousEnd)->count();

        $this->totalSales = (int) $this->revenueOrdersBetween($start, $end)->sum('total_amount');
        $previousTotalSales = (int) $this->revenueOrdersBetween($previousStart, $previousEnd)->sum('total_amount');

        $revenueOrdersCount = $this->revenueOrdersBetween($start, $end)->count();
        $previousRevenueOrdersCount = $this->revenueOrdersBetween($previousStart, $previousEnd)->count();

        $this->averageOrderValue = $revenueOrdersCount > 0 ? intdiv($this->totalSales, $revenueOrdersCount) : 0;
        $previousAverageOrderValue = $previousRevenueOrdersCount > 0 ? intdiv($previousTotalSales, $previousRevenueOrdersCount) : 0;

        $this->visitorsCount = 0;
        $this->salesChange = $this->percentChange($this->totalSales, $previousTotalSales);
        $this->ordersChange = $this->percentChange($this->ordersCount, $previousOrdersCount);
        $this->aovChange = $this->percentChange($this->averageOrderValue, $previousAverageOrderValue);
        $this->visitorsChange = 0.0;

        $this->loadChart();
        $this->loadTopProducts();
        $this->loadFunnel();
    }

    public function loadChart(): void
    {
        [$start, $end] = $this->currentRange();

        $counts = $this->ordersBetween($start, $end)
            ->selectRaw('date(placed_at) as order_date, count(*) as aggregate')
            ->groupBy('order_date')
            ->pluck('aggregate', 'order_date');

        $this->ordersChartData = collect(CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay()))
            ->map(fn (CarbonInterface $date): array => [
                'date' => $date->toDateString(),
                'label' => $date->format('M j'),
                'count' => (int) ($counts[$date->toDateString()] ?? 0),
            ])
            ->values()
            ->all();

        $this->maxOrdersChartCount = max(1, max(array_column($this->ordersChartData, 'count') ?: [0]));
    }

    public function loadTopProducts(): void
    {
        [$start, $end] = $this->currentRange();

        $this->topProducts = OrderLine::query()
            ->selectRaw('order_lines.title_snapshot as title, sum(order_lines.quantity) as units_sold, sum(order_lines.total_amount) as revenue')
            ->join('orders', 'orders.id', '=', 'order_lines.order_id')
            ->where('orders.store_id', $this->storeId)
            ->whereBetween('orders.placed_at', [$start, $end])
            ->whereIn('orders.financial_status', $this->revenueStatuses())
            ->groupBy('order_lines.product_id', 'order_lines.title_snapshot')
            ->orderByDesc('revenue')
            ->limit(5)
            ->get()
            ->map(fn (OrderLine $line): array => [
                'title' => (string) $line->title,
                'units_sold' => (int) $line->units_sold,
                'revenue' => (int) $line->revenue,
            ])
            ->all();
    }

    public function loadFunnel(): void
    {
        [$start, $end] = $this->currentRange();

        $this->funnelData = [
            'visits' => 0,
            'add_to_cart' => Cart::withoutGlobalScopes()
                ->where('store_id', $this->storeId)
                ->whereBetween('created_at', [$start, $end])
                ->count(),
            'checkout_started' => Checkout::withoutGlobalScopes()
                ->where('store_id', $this->storeId)
                ->whereBetween('created_at', [$start, $end])
                ->count(),
            'checkout_completed' => $this->ordersCount,
        ];
    }

    #[Computed]
    public function formattedTotalSales(): string
    {
        return Money::format($this->totalSales, $this->storeCurrency);
    }

    #[Computed]
    public function formattedAov(): string
    {
        return Money::format($this->averageOrderValue, $this->storeCurrency);
    }

    public function render(): mixed
    {
        return view('livewire.admin.dashboard')->layout('layouts.app', [
            'title' => __('Dashboard'),
        ]);
    }

    /**
     * @return array{0: CarbonInterface, 1: CarbonInterface}
     */
    private function currentRange(): array
    {
        return match ($this->dateRange) {
            'today' => [now()->startOfDay(), now()->endOfDay()],
            'last_7_days' => [now()->subDays(6)->startOfDay(), now()->endOfDay()],
            'custom' => $this->customRange() ?? [now()->subDays(29)->startOfDay(), now()->endOfDay()],
            default => [now()->subDays(29)->startOfDay(), now()->endOfDay()],
        };
    }

    /**
     * @return array{0: CarbonInterface, 1: CarbonInterface}
     */
    private function previousRange(CarbonInterface $start, CarbonInterface $end): array
    {
        $days = max(1, $start->copy()->startOfDay()->diffInDays($end->copy()->startOfDay()) + 1);
        $previousEnd = $start->copy()->subSecond();

        return [$start->copy()->subDays($days)->startOfDay(), $previousEnd];
    }

    /**
     * @return array{0: CarbonInterface, 1: CarbonInterface}|null
     */
    private function customRange(): ?array
    {
        if (! $this->customStartDate || ! $this->customEndDate) {
            return null;
        }

        try {
            $start = Carbon::parse($this->customStartDate)->startOfDay();
            $end = Carbon::parse($this->customEndDate)->endOfDay();
        } catch (Throwable) {
            return null;
        }

        if ($end->lessThan($start)) {
            return [$end->startOfDay(), $start->endOfDay()];
        }

        return [$start, $end];
    }

    /**
     * @return Builder<Order>
     */
    private function ordersBetween(CarbonInterface $start, CarbonInterface $end): Builder
    {
        return Order::withoutGlobalScopes()
            ->where('store_id', $this->storeId)
            ->whereBetween('placed_at', [$start, $end]);
    }

    /**
     * @return Builder<Order>
     */
    private function revenueOrdersBetween(CarbonInterface $start, CarbonInterface $end): Builder
    {
        return $this->ordersBetween($start, $end)
            ->whereIn('financial_status', $this->revenueStatuses());
    }

    /**
     * @return list<string>
     */
    private function revenueStatuses(): array
    {
        return [
            FinancialStatus::Paid->value,
            FinancialStatus::PartiallyRefunded->value,
        ];
    }

    private function percentChange(int $current, int $previous): float
    {
        if ($previous === 0) {
            return $current > 0 ? 100.0 : 0.0;
        }

        return round((($current - $previous) / $previous) * 100, 1);
    }
}


Output:
Chunk ID: 21976c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1974
Output:

            <flux:heading size="xl">Dashboard</flux:heading>
            <flux:text class="mt-1">Sales, orders, and product movement for the selected store.</flux:text>

            <flux:select wire:model.live="dateRange" aria-label="Date range">
                <flux:select.option value="today">Today</flux:select.option>
                <flux:select.option value="last_7_days">Last 7 days</flux:select.option>
                <flux:select.option value="last_30_days">Last 30 days</flux:select.option>
                <flux:select.option value="custom">Custom</flux:select.option>
            </flux:select>

            <flux:input type="date" wire:model.live="customStartDate" :disabled="$dateRange !== 'custom'" aria-label="Custom range start" />
            <flux:input type="date" wire:model.live="customEndDate" :disabled="$dateRange !== 'custom'" aria-label="Custom range end" />

        @foreach ([
            ['label' => 'Total sales', 'value' => $this->formattedTotalSales, 'change' => $salesChange, 'icon' => 'banknotes'],
            ['label' => 'Orders', 'value' => number_format($ordersCount), 'change' => $ordersChange, 'icon' => 'shopping-bag'],
            ['label' => 'Average order', 'value' => $this->formattedAov, 'change' => $aovChange, 'icon' => 'receipt-percent'],
            ['label' => 'Visitors', 'value' => number_format($visitorsCount), 'change' => $visitorsChange, 'icon' => 'users'],
        ] as $metric)
            @php
                $change = (float) $metric['change'];
                $changeColor = $change > 0 ? 'text-emerald-600 dark:text-emerald-400' : ($change < 0 ? 'text-rose-600 dark:text-rose-400' : 'text-zinc-500 dark:text-zinc-400');
            @endphp

{{ $metric['label'] }}

{{ $metric['value'] }}

                        <flux:icon :name="$metric['icon']" class="size-5" />

                    <flux:icon :name="$change >= 0 ? 'arrow-trending-up' : 'arrow-trending-down'" class="size-4" />
                    {{ $change > 0 ? '+' : '' }}{{ number_format($change, 1) }}%

        @endforeach

                    <flux:heading size="lg">Orders over time</flux:heading>
                    <flux:text class="mt-1">Daily order count</flux:text>

                @foreach ($ordersChartData as $point)
                    @php
                        $height = $point['count'] === 0 ? 2 : max(8, (int) round(($point['count'] / $maxOrdersChartCount) * 100));
                    @endphp

                        @if ($loop->first || $loop->last || $loop->iteration % 7 === 0)

{{ $point['label'] }}

                        @endif

                @endforeach

            <flux:heading size="lg">Top products</flux:heading>
            <flux:text class="mt-1">Ranked by order-line revenue</flux:text>

                @forelse ($topProducts as $product)

{{ $product['title'] }}

{{ number_format($product['units_sold']) }} sold

                            {{ \App\Support\Money::format($product['revenue'], $storeCurrency) }}

                @empty

                        <flux:icon name="cube" class="mx-auto size-8 text-zinc-400" />
                        <flux:text class="mt-3">No product sales in this range.</flux:text>

                @endforelse

                <flux:heading size="lg">Conversion funnel</flux:heading>
                <flux:text class="mt-1">Current range activity</flux:text>

        @php
            $maxFunnelValue = max(1, max($funnelData));
            $funnelSteps = [
                'visits' => 'Visits',
                'add_to_cart' => 'Carts',
                'checkout_started' => 'Checkouts',
                'checkout_completed' => 'Orders',
            ];
        @endphp

            @foreach ($funnelSteps as $key => $label)
                @php
                    $value = (int) $funnelData[$key];
                    $width = $value === 0 ? 2 : max(8, (int) round(($value / $maxFunnelValue) * 100));
                @endphp

{{ $label }}

{{ number_format($value) }}

            @endforeach


Output:
Chunk ID: 752334
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 881
Output:
<?php

use App\Livewire\Admin\Dashboard as AdminDashboard;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Store;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminDashboardStore(): Store
{
    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    app()->instance('current_store', $store);

    return $store;
}

function adminDashboardUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

function adminDashboardOrder(Store $store, string $title, int $quantity, int $unitPrice, array $orderAttributes = []): Order
{
    $product = Product::factory()
        ->withDefaultVariant($unitPrice)
        ->create([
            'store_id' => $store->getKey(),
            'title' => $title,
        ]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();
    $total = $quantity * $unitPrice;
    $order = Order::factory()->paid()->create(array_merge([
        'store_id' => $store->getKey(),
        'subtotal_amount' => $total,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => $total,
        'placed_at' => now(),
    ], $orderAttributes));

    OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'variant_id' => $variant->getKey(),
        'title_snapshot' => $title,
        'sku_snapshot' => 'DASH-'.str($title)->slug()->upper(),
        'quantity' => $quantity,
        'unit_price_amount' => $unitPrice,
        'total_amount' => $total,
    ]);

    return $order;
}

test('admin dashboard route requires authentication and renders store scoped metrics', function (): void {
    $store = adminDashboardStore();
    adminDashboardOrder($store, 'Dashboard Jacket', 2, 2500);
    adminDashboardOrder($store, 'Dashboard Cap', 1, 2000);

    $otherStore = Store::factory()->create();
    adminDashboardOrder($otherStore, 'Other Store Product', 1, 9900);

    $this->get('/admin')->assertRedirect('/admin/login');

    $this->actingAs(adminDashboardUser())
        ->withSession(['current_store_id' => $store->getKey()])
        ->get('/admin')
        ->assertSuccessful()
        ->assertSee('Dashboard')
        ->assertSee('70.00 EUR')
        ->assertSee('Dashboard Jacket')
        ->assertDontSee('Other Store Product');
});

test('admin dashboard recalculates kpis when date range changes', function (): void {
    $store = adminDashboardStore();
    $user = adminDashboardUser();

    adminDashboardOrder($store, 'Today Product', 1, 6000);
    adminDashboardOrder($store, 'Older Product', 1, 4000, [
        'placed_at' => now()->subDays(15),
    ]);

    Livewire::actingAs($user)
        ->test(AdminDashboard::class)
        ->assertSet('ordersCount', 2)
        ->assertSet('totalSales', 10000)
        ->set('dateRange', 'today')
        ->assertSet('ordersCount', 1)
        ->assertSet('totalSales', 6000)
        ->set('dateRange', 'custom')
        ->set('customStartDate', now()->subDays(20)->toDateString())
        ->set('customEndDate', now()->subDays(10)->toDateString())
        ->assertSet('ordersCount', 1)
        ->assertSet('totalSales', 4000);
});


The dashboard code has been reviewed after formatting; the final diff check is clean and there are no generated artifacts left. I’m staging and committing this dashboard slice.


git add app/Livewire/Admin/Dashboard.php resources/views/livewire/admin/dashboard.blade.php tests/Feature/Admin/DashboardTest.php routes/web.php specs/progress.md


Output:
Chunk ID: c3638b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


A  app/Livewire/Admin/Dashboard.php
A  resources/views/livewire/admin/dashboard.blade.php
M  routes/web.php
M  specs/progress.md
A  tests/Feature/Admin/DashboardTest.php


 app/Livewire/Admin/Dashboard.php                   | 296 +++++++++++++++++++++
 resources/views/livewire/admin/dashboard.blade.php | 142 ++++++++++
 routes/web.php                                     |   3 +-
 specs/progress.md                                  |  24 +-
 tests/Feature/Admin/DashboardTest.php              | 110 ++++++++
 5 files changed, 568 insertions(+), 7 deletions(-)


Output:
Chunk ID: b7d5a6
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 43
Output:
A  app/Livewire/Admin/Dashboard.php
A  resources/views/livewire/admin/dashboard.blade.php
M  routes/web.php
M  specs/progress.md
A  tests/Feature/Admin/DashboardTest.php


Output:
Chunk ID: bc6ac5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 98
Output:
 app/Livewire/Admin/Dashboard.php                   | 296 +++++++++++++++++++++
 resources/views/livewire/admin/dashboard.blade.php | 142 ++++++++++
 routes/web.php                                     |   3 +-
 specs/progress.md                                  |  24 +-
 tests/Feature/Admin/DashboardTest.php              | 110 ++++++++
 5 files changed, 568 insertions(+), 7 deletions(-)


Output:
Chunk ID: 0255f3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


git commit -m "Add admin dashboard metrics"


git commit -m "Add admin dashboard metrics"

[2026-05-04-codex-5-5-goal 90a894bb] Add admin dashboard metrics
 5 files changed, 568 insertions(+), 7 deletions(-)
 create mode 100644 app/Livewire/Admin/Dashboard.php
 create mode 100644 resources/views/livewire/admin/dashboard.blade.php
 create mode 100644 tests/Feature/Admin/DashboardTest.php


Output:
Chunk ID: a58a9c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 75
Output:
[2026-05-04-codex-5-5-goal 90a894bb] Add admin dashboard metrics
 5 files changed, 568 insertions(+), 7 deletions(-)
 create mode 100644 app/Livewire/Admin/Dashboard.php
 create mode 100644 resources/views/livewire/admin/dashboard.blade.php
 create mode 100644 tests/Feature/Admin/DashboardTest.php


Committed the dashboard slice as `90a894bb Add admin dashboard metrics`. I’m continuing with the next Phase 6 gap: admin customer management, starting from the spec and existing customer/order models.


rg -n "## [0-9]+\. Customers|Customers|Customer Detail|customer" specs/03-ADMIN-UI.md


laravel/framework livewire/livewire livewire/flux pestphp/pest

Livewire full page components pagination filters form actions tests

Flux input select button badge modal textarea form table

Laravel Eloquent relationship counts aggregates validation authorization

Pest Livewire component tests forms assertions


sed -n '1,220p' database/factories/CustomerFactory.php


sed -n '1,220p' database/migrations/2026_05_03_211556_create_customers_table.php


sed -n '1,220p' database/migrations/2026_05_04_000408_create_customer_addresses_table.php


rg -n "## [0-9]+\. Customers|Customers|Customer Detail|customer" specs/03-ADMIN-UI.md

rg -n "## [0-9]+\\. Customers|Customers|Customer Detail|customer" specs/03-ADMIN-UI.md

Question: ## [0-9]+\. Customers|Customers|Customer Detail|customer

57:|  [users]        Customers     |
87:Customers
88:  Customers                      -> admin.customers.index
874:| `search` | `string` | Search by order number or customer email |
887:| `orders` | Paginated query with search, status filter, sorting. Eager loads `customer`. |
948:| `order` | `Order` | The order model (eager loaded with lines, payments, fulfillments, customer) |
977:| [Create fulfillment]  [Refund]               | > View customer    |
1074:- "View customer" link (shown only if customer exists, links to customer detail page)
1134:## 9. Customers List and Detail
1136:### 9.1 Customers List
1138:**Route:** `GET /admin/customers`
1140:**Livewire component:** `App\Livewire\Admin\Customers\Index`
1142:**Blade view:** `resources/views/livewire/admin/customers/index.blade.php`
1152:| `customers` | Paginated, searched customers with `withCount('orders')` and `withSum('orders', 'total_amount')` |
1156:- Heading "Customers"
1159:- Each row links to customer detail
1161:### 9.2 Customer Detail
1163:**Route:** `GET /admin/customers/{customer}`
1165:**Livewire component:** `App\Livewire\Admin\Customers\Show`
1167:**Blade view:** `resources/views/livewire/admin/customers/show.blade.php`
1173:| `customer` | `Customer` | The customer model |
1180:| `mount(Customer $customer)` | Loads customer with orders and addresses |
1291:| `onePerCustomer` | `bool` | Limit to one use per customer |
1312:|  ( ) Discount code - Customers enter a code at checkout          |
1338:|  [x] Limit to one use per customer                               |
1353:**Type section:** Two radio buttons - "Discount code" (description: "Customers enter a code at checkout") and "Automatic discount" (description: "Applied automatically at checkout"). Selecting type is live-bound.
1366:- "Limit to one use per customer" checkbox
2116:- Event type select with options: order.created, order.updated, order.cancelled, product.created, product.updated, product.deleted, customer.created, checkout.completed, fulfillment.created, refund.created
2412:| `App\Livewire\Admin\Customers\Index` | `livewire.admin.customers.index` | `GET /admin/customers` |
2413:| `App\Livewire\Admin\Customers\Show` | `livewire.admin.customers.show` | `GET /admin/customers/{customer}` |


Output:
Chunk ID: 05fde2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


Output:
Chunk ID: 4da78f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 564
Output:
57:|  [users]        Customers     |
87:Customers
88:  Customers                      -> admin.customers.index
874:| `search` | `string` | Search by order number or customer email |
887:| `orders` | Paginated query with search, status filter, sorting. Eager loads `customer`. |
948:| `order` | `Order` | The order model (eager loaded with lines, payments, fulfillments, customer) |
977:| [Create fulfillment]  [Refund]               | > View customer    |
1074:- "View customer" link (shown only if customer exists, links to customer detail page)
1134:## 9. Customers List and Detail
1136:### 9.1 Customers List
1138:**Route:** `GET /admin/customers`
1140:**Livewire component:** `App\Livewire\Admin\Customers\Index`
1142:**Blade view:** `resources/views/livewire/admin/customers/index.blade.php`
1152:| `customers` | Paginated, searched customers with `withCount('orders')` and `withSum('orders', 'total_amount')` |
1156:- Heading "Customers"
1159:- Each row links to customer detail
1161:### 9.2 Customer Detail
1163:**Route:** `GET /admin/customers/{customer}`
1165:**Livewire component:** `App\Livewire\Admin\Customers\Show`
1167:**Blade view:** `resources/views/livewire/admin/customers/show.blade.php`
1173:| `customer` | `Customer` | The customer model |
1180:| `mount(Customer $customer)` | Loads customer with orders and addresses |
1291:| `onePerCustomer` | `bool` | Limit to one use per customer |
1312:|  ( ) Discount code - Customers enter a code at checkout          |
1338:|  [x] Limit to one use per customer                               |
1353:**Type section:** Two radio buttons - "Discount code" (description: "Customers enter a code at checkout") and "Automatic discount" (description: "Applied automatically at checkout"). Selecting type is live-bound.
1366:- "Limit to one use per customer" checkbox
2116:- Event type select with options: order.created, order.updated, order.cancelled, product.created, product.updated, product.deleted, customer.created, checkout.completed, fulfillment.created, refund.created
2412:| `App\Livewire\Admin\Customers\Index` | `livewire.admin.customers.index` | `GET /admin/customers` |
2413:| `App\Livewire\Admin\Customers\Show` | `livewire.admin.customers.show` | `GET /admin/customers/{customer}` |


# Search Results

## Components Pagination > Large result set
Components Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux

Large result set

When working with large result sets, the pagination component automatically adapts to show a reasonable number of page links. It shows the first and last pages, along with a window of pages around the current page, and adds ellipses for any gaps to ensure efficient navigation through numerous pages.

<flux:pagination :paginator="$orders" />

---

## Components Field > With badge
Components Field | livewire/flux@2.x | Source: https://fluxui.dev/components/field#livewire/flux

With badge

Badges allow you to enhance a field with additional information such as being "required" or "optional" when it might not be expected.

<flux:field>    <flux:label badge="Required">Email</flux:label>    <flux:input type="email" required />    <flux:error name="email" /></flux:field><flux:field>    <flux:label badge="Optional">Phone number</flux:label>    <flux:input type="phone" placeholder="(555) 555-5555" mask="(999) 999-9999"  />    <flux:error name="phone" /></flux:field>

---

## Urls URLs for Named Routes Eloquent Models
Urls | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/urls.md

#### Eloquent Models

You will often be generating URLs using the route key (typically the primary key) of [Eloquent models](/docs/{{version}}/eloquent). For this reason, you may pass Eloquent models as parameter values. The `route` helper will automatically extract the model's route key:

```php
echo route('post.show', ['post' => $post]);
```

---

## Understanding Nesting Every component is independent {#every-component-is-an-island}
Understanding Nesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/understanding-nesting

## Every component is independent {#every-component-is-an-island}

In Livewire, every component on a page tracks its state and makes updates independently of other components.

For example, consider the following `Posts` and nested `ShowPost` components:

```php
<?php

namespace App\Livewire;

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Posts extends Component
{
    public $postLimit = 2;

    public function render()
    {
        return view('livewire.posts', [
            'posts' => Auth::user()->posts()
                ->limit($this->postLimit)->get(),
        ]);
    }
}
```

```blade
<div>
    Post Limit: <input type="number" wire:model.live="postLimit">

    @foreach ($posts as $post)
        <livewire:show-post :$post :wire:key="$post->id">
    @endforeach
</div>
```

```php
<?php

namespace App\Livewire;

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

class ShowPost extends Component
{
    public Post $post;

    public function render()
    {
        return view('livewire.show-post');
    }
}
```

```blade
<div>
    <h1>{{ $post->title }}</h1>

    <p>{{ $post->content }}</p>

    <button wire:click="$refresh">Refresh post</button>
</div>
```

Here's what the HTML for the entire component tree might look like on initial page load:

```html
<div wire:id="123" wire:snapshot="...">
    Post Limit: <input type="number" wire:model.live="postLimit">

    <div wire:id="456" wire:snapshot="...">
        <h1>The first post</h1>

        <p>Post content</p>

        <button wire:click="$refresh">Refresh post</button>
    </div>

    <div wire:id="789" wire:snapshot="...">
        <h1>The second post</h1>

        <p>Post content</p>

        <button wire:click="$refresh">Refresh post</button>
    </div>
</div>
```

Notice that the parent component contains both its rendered template and the rendered templates of all the components nested within it.

Because each component is independent, they each have their own IDs and snapshots (`wire:id` and `wire:snapshot`) embedded in their HTML for Livewire's JavaScript core to extract and track.

Let's consider a few different update scenarios to see the differences in how Livewire handles different levels of nesting.

### Updating a child

If you were to click the "Refresh post" button in one of the child `show-post` components, here is what would be sent to the server:

```js
{
    memo: { name: 'show-post', id: '456' },

    state: { ... },
}
```

Here's the HTML that would get sent back:

```html
<div wire:id="456">
    <h1>The first post</h1>

    <p>Post content</p>

    <button wire:click="$refresh">Refresh post</button>
</div>
```

The important thing to note here is that when an update is triggered on a child component, only that component's data is sent to the server, and only that component is re-rendered.

Now let's look at the less intuitive scenario: updating a parent component.

### Updating the parent

As a reminder, here's the Blade template of the parent `Posts` component:

```blade
<div>
    Post Limit: <input type="number" wire:model.live="postLimit">

    @foreach ($posts as $post)
        <livewire:show-post :$post :wire:key="$post->id">
    @endforeach
</div>
```

If a user changes the "Post Limit" value from `2` to `1`, an update will be solely triggered on the parent.

Here's an example of what the request payload might look like:

```js
{
    updates: { postLimit: 1 },

    snapshot: {
        memo: { name: 'posts', id: '123' },

        state: { postLimit: 2, ... },
    },
}
```

As you can see, only the snapshot for the parent `Posts` component is sent along to the server.

An important question you might be asking yourself is: what happens when the parent component re-renders and encounters the child `show-post` components? How will it re-render the children if their snapshots haven't been included in the request payload?

The answer is: they won't be re-rendered.

When Livewire renders the `Posts` component, it will render placeholders for any child components it encounters.

Here is an example of what the rendered HTML for the `Posts` component might be after the above update:

```html
<div wire:id="123">
    Post Limit: <input type="number" wire:model.live="postLimit">

    <div wire:id="456"></div>
</div>
```

As you can see, only one child has been rendered because `postLimit` was updated to `1`. However, you will also notice that instead of the full child component, there is only an empty `<div></div>` with the matching `wire:id` attribute.

When this HTML is received on the frontend, Livewire will _morph_ the old HTML for this component into this new HTML, but intelligently skip any child component placeholders.

The effect is that, after _morphing_, the final DOM content of the parent `Posts` component will be the following:

```html
<div wire:id="123">
    Post Limit: <input type="number" wire:model.live="postLimit">

    <div wire:id="456">
        <h1>The first post</h1>

        <p>Post content</p>

        <button wire:click="$refresh">Refresh post</button>
    </div>
</div>
```

---

## Testing Configuring Pest for view-based components
Testing | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing

## Configuring Pest for view-based components

If you're writing tests alongside your view-based components (single-file or multi-file), you'll need to configure Pest to recognize these test files.

First, update your `tests/Pest.php` file to include the `resources/views` directory:

```php
pest()->extend(Tests\TestCase::class)
    // ...
    ->in('Feature', '../resources/views');
```

This tells Pest to use your `TestCase` base class for tests found in both the `tests/Feature` directory and anywhere within `resources/views`.

Next, update your `phpunit.xml` file to include a test suite for component tests:

```xml
<testsuite name="Components">
    <directory suffix=".test.php">resources/views</directory>
</testsuite>
```

Now Pest will recognize and run tests located next to your components when you run `./vendor/bin/pest`.

---

## Components Table > Pagination
Components Table | livewire/flux@2.x | Source: https://fluxui.dev/components/table#livewire/flux

Pagination

Allow users to navigate through different pages of data by passing in any model paginator to the paginate prop.

<flux:table :paginate="$orders">    </flux:table>

---

## Wire Model Input fields
Wire Model | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-model

## Input fields

Livewire supports most native input elements out of the box. Meaning you should just be able to attach `wire:model` to any input element in the browser and easily bind properties to them.

Here's a comprehensive list of the different available input types and how you use them in a Livewire context.

### Text inputs

First and foremost, text inputs are the bedrock of most forms. Here's how to bind a property named "title" to one:

```blade
<input type="text" wire:model="title">
```

### Textarea inputs

Textarea elements are similarly straightforward. Simply add `wire:model` to a textarea and the value will be bound:

```blade
<textarea type="text" wire:model="content"></textarea>
```

If the "content" value is initialized with a string, Livewire will fill the textarea with that value - there's no need to do something like the following:

```blade
<!-- Warning: This snippet demonstrates what NOT to do... -->

<textarea type="text" wire:model="content">{{ $content }}</textarea>
```

### Checkboxes

Checkboxes can be used for single values, such as when toggling a boolean property. Or, checkboxes may be used to toggle a single value in a group of related values. We'll discuss both scenarios:

#### Single checkbox

At the end of a signup form, you might have a checkbox allowing the user to opt-in to email updates. You might call this property `$receiveUpdates`. You can easily bind this value to the checkbox using `wire:model`:

```blade
<input type="checkbox" wire:model="receiveUpdates">
```

Now when the `$receiveUpdates` value is `false`, the checkbox will be unchecked. Of course, when the value is `true`, the checkbox will be checked.

#### Multiple checkboxes

Now, let's say in addition to allowing the user to decide to receive updates, you have an array property in your class called `$updateTypes`, allowing the user to choose from a variety of update types:

```php
public $updateTypes = [];
```

By binding multiple checkboxes to the `$updateTypes` property, the user can select multiple update types and they will be added to the `$updateTypes` array property:

```blade
<input type="checkbox" value="email" wire:model="updateTypes">
<input type="checkbox" value="sms" wire:model="updateTypes">
<input type="checkbox" value="notification" wire:model="updateTypes">
```

For example, if the user checks the first two boxes but not the third, the value of `$updateTypes` will be: `["email", "sms"]`

### Radio buttons

To toggle between two different values for a single property, you may use radio buttons:

```blade
<input type="radio" value="yes" wire:model="receiveUpdates">
<input type="radio" value="no" wire:model="receiveUpdates">
```

### Select dropdowns

Livewire makes it simple to work with `<select>` dropdowns. When adding `wire:model` to a dropdown, the currently selected value will be bound to the provided property name and vice versa.

In addition, there's no need to manually add `selected` to the option that will be selected - Livewire handles that for you automatically.

Below is an example of a select dropdown filled with a static list of states:

```blade
<select wire:model="state">
    <option value="AL">Alabama</option>
    <option value="AK">Alaska</option>
    <option value="AZ">Arizona</option>
    ...
</select>
```

When a specific state is selected, for example, "Alaska", the `$state` property on the component will be set to `AK`. If you would prefer the value to be set to "Alaska" instead of "AK", you can leave the `value=""` attribute off the `<option>` element entirely.

Often, you may build your dropdown options dynamically using Blade:

```blade
<select wire:model="state">
    @foreach (\App\Models\State::all() as $state)
        <option value="{{ $state->id }}">{{ $state->label }}</option>
    @endforeach
</select>
```

If you don't have a specific option selected by default, you may want to show a muted placeholder option by default, such as "Select a state":

```blade
<select wire:model="state">
    <option disabled value="">Select a state...</option>

    @foreach (\App\Models\State::all() as $state)
        <option value="{{ $state->id }}">{{ $state->label }}</option>
    @endforeach
</select>
```

As you can see, there is no "placeholder" attribute for a select menu like there is for text inputs. Instead, you have to add a `disabled` option element as the first option in the list.

### Dependent select dropdowns

Sometimes you may want one select menu to be dependent on another. For example, a list of cities that changes based on which state is selected.

For the most part, this works as you'd expect, however there is one important gotcha: You must add a `wire:key` to the changing select so that Livewire properly refreshes its value when the options change.

Here's an example of two selects, one for states, one for cities. When the state select changes, the options in the city select will change properly:

```blade
<!-- States select menu... -->
<select wire:model.live="selectedState">
    @foreach (State::all() as $state)
        <option value="{{ $state->id }}">{{ $state->label }}</option>
    @endforeach
</select>

<!-- Cities dependent select menu... -->
<select wire:model.live="selectedCity" wire:key="{{ $selectedState }}"> <!-- [tl! highlight] -->
    @foreach (City::whereStateId($selectedState->id)->get() as $city)
        <option value="{{ $city->id }}">{{ $city->label }}</option>
    @endforeach
</select>
```

Again, the only thing non-standard here is the `wire:key` that has been added to the second select. This ensures that when the state changes, the "selectedCity" value will be reset properly.

### Multi-select dropdowns

If you are using a "multiple" select menu, Livewire works as expected. In this example, states will be added to the `$states` array property when they are selected and removed if they are deselected:

```blade
<select wire:model="states" multiple>
    <option value="AL">Alabama</option>
    <option value="AK">Alaska</option>
    <option value="AZ">Arizona</option>
    ...
</select>
```

---

## Upgrade Upgrading To 12.0 From 11.x Eloquent Models and UUIDv7
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Models and UUIDv7

**Likelihood Of Impact: Medium**

The `HasUuids` trait now returns UUIDs that are compatible with version 7 of the UUID spec (ordered UUIDs). If you would like to continue using ordered UUIDv4 strings for your model's IDs, you should now use the `HasVersion4Uuids` trait:

```php
use Illuminate\Database\Eloquent\Concerns\HasUuids; // [tl! remove]
use Illuminate\Database\Eloquent\Concerns\HasVersion4Uuids as HasUuids; // [tl! add]
```

The `HasVersion7Uuids` trait has been removed. If you were previously using this trait, you should use the `HasUuids` trait instead, which now provides the same behavior.

---

## Troubleshooting Component mismatches
Troubleshooting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/troubleshooting

## Component mismatches

When interacting with Livewire components on your page, you may encounter odd behavior or error messages like the following:

```
Error: Component already initialized
```

```
Error: Snapshot missing on Livewire component with id: ...
```

There are lots of reasons why you may encounter these messages, but the most common one is forgetting to add `wire:key` to elements and components inside a `@foreach` loop.

### Adding `wire:key`

Any time you have a loop or switch case in your Blade templates using something like `@foreach`, or `@switch`/`@case`, you need to add `wire:key` to the opening tag of the first element:

```blade
@foreach($posts as $post)
    <div wire:key="{{ $post->id }}"> <!-- [tl! highlight] -->
        ...
    </div>
@endforeach
```

This ensures that Livewire can keep track of different elements in the loop or conditional when it changes.

The same applies to Livewire components within a loop:

```blade
@foreach($posts as $post)
    <livewire:show-post :$post :wire:key="$post->id" /> <!-- [tl! highlight] -->
@endforeach
```

However, here's a tricky scenario you might not have assumed:

When you have a Livewire component deeply nested inside a `@foreach` loop, you STILL need to add a key to it. For example:

```blade
@foreach($posts as $post)
    <div wire:key="{{ $post->id }}">
        ...
        <livewire:show-post :$post :wire:key="$post->id" /> <!-- [tl! highlight] -->
        ...
    </div>
@endforeach
```

Without the key on the nested Livewire component, Livewire will be unable to match the looped components up between network requests.

#### Prefixing keys

Another tricky scenario you may run into is having duplicate keys within the same component. This often results from using model IDs as keys, which can sometimes collide.

Here's an example where we need to add a `post-` and an `author-` prefix to designate each set of keys as unique. Otherwise, if you have a `$post` and `$author` model with the same ID, you would have an ID collision:

```blade
<div>
    @foreach($posts as $post)
        <div wire:key="post-{{ $post->id }}">...</div> <!-- [tl! highlight] -->
    @endforeach

    @foreach($authors as $author)
        <div wire:key="author-{{ $author->id }}">...</div> <!-- [tl! highlight] -->
    @endforeach
</div>
```

---

## Components Pagination > Simple paginator
Components Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux

Simple paginator

Use the simple paginator when working with large datasets where counting the total number of results would be expensive. The simple paginator provides "Previous" and "Next" buttons without displaying the total number of pages or records.

<flux:pagination :paginator="$orders" />

---

## Eloquent Relationships Aggregating Related Models
Eloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md

## Aggregating Related Models

---

## Components Pagination > Pagination
Components Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux

    Pagination

Display a series of buttons to navigate through a list of items.

<flux:pagination :paginator="$orders" />

---

## Wire Confirm Prompting users for input
Wire Confirm | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-confirm

## Prompting users for input

For even more dangerous actions such as deleting a user's account entirely, you may want to present them with a confirmation prompt which they would need to type in a specific string of characters to confirm the action.

Livewire provides a helpful `.prompt` modifier, that when applied to `wire:confirm`, it will prompt the user for input and only confirm the action if the input matches (case-sensitive) the provided string (designated by a "|" (pipe) character at the end if the `wire:confirm` value):

```blade
<button
    type="button"
    wire:click="delete"
    wire:confirm.prompt="Are you sure?\n\nType DELETE to confirm|DELETE"
>
    Delete account <!-- [tl! highlight:-2,1] -->
</button>
```

When a user presses "Delete account", the action will only be performed if "DELETE" is entered into the prompt, otherwise, the action will be cancelled.

---

## Scout Searching Where Clauses Customizing the Eloquent Results Query
Scout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md

#### Customizing the Eloquent Results Query

After Scout retrieves a list of matching Eloquent models from your application's search engine, Eloquent is used to retrieve all of the matching models by their primary keys. You may customize this query by invoking the `query` method. The `query` method accepts a closure that will receive the Eloquent query builder instance as an argument:

```php
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with('invoices'))
    ->get();
```

When using a third-party engine, this callback is invoked after the relevant models have already been retrieved from the search engine, so it should not be used for "filtering" results — use [Scout where clauses](#where-clauses) instead. However, when using the database engine, the `query` method's constraints are applied directly to the database query, so you may use it for filtering as well.

---

## Redirecting Redirecting to full-page components
Redirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting

## Redirecting to full-page components

Because Livewire uses Laravel's built-in redirection feature, you can use all of the redirection methods available to you in a typical Laravel application.

For example, if you are using a Livewire component as a full-page component for a route like so:

```php
Route::livewire('/posts', 'pages::show-posts');
```

You can redirect to it simply by using the route path:

```php
public function save()
{
    // ...

    $this->redirect('/posts');
}
```

---

## Forms Throttling input
Forms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms

## Throttling input

As stated previously, when an input debounce is applied to a field, a network request will not be sent until the user has stopped typing for a certain amount of time. This means if the user continues typing a long message, a network request won't be sent until the user is finished.

Sometimes this isn't the desired behavior, and you would rather send a request as the user types, not when they've finished or taken a break.

In these cases, you can instead use `.throttle` to signify a time interval to send network requests:

```blade
<input type="text" wire:model.live.throttle.150ms="title" >
```

In the above example, as a user is typing continuously in the "title" field, a network request will be sent every 150 milliseconds until the user is finished.

---

## Styles When to use component styles
Styles | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/styles

## When to use component styles

**Use scoped styles when:**
- Styling is specific to a single component
- You want to avoid CSS class name collisions
- You're building reusable, self-contained components

**Use global styles when:**
- You need to style elements outside your component
- You're defining utility classes used across multiple components
- You're overriding third-party library styles

**Use `@assets` for external stylesheets:**
- When loading CSS from a CDN
- When including third-party library styles

```blade
@assets
<link rel="stylesheet" href="https://cdn.example.com/library.css">
@endassets
```

---

## Lazy Full-page lazy loading
Lazy | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/lazy

## Full-page lazy loading

You can lazy load or defer full-page Livewire components using route methods.

### Lazy loading full pages

Use `->lazy()` to load the component when it enters the viewport:

```php
Route::livewire('/dashboard', 'pages::dashboard')->lazy();
```

### Deferring full pages

Use `->defer()` to load the component immediately after the page loads:

```php
Route::livewire('/dashboard', 'pages::dashboard')->defer();
```

### Disabling lazy/defer loading

If a component is lazy or deferred by default (via the `#[Lazy]` or `#[Defer]` attribute), you can opt-out using `enabled: false`:

```php
Route::livewire('/dashboard', 'pages::dashboard')->lazy(enabled: false);
Route::livewire('/dashboard', 'pages::dashboard')->defer(enabled: false);
```

---

## Responses Redirects Redirecting to Named Routes Populating Parameters via Eloquent Models
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

#### Populating Parameters via Eloquent Models

If you are redirecting to a route with an "ID" parameter that is being populated from an Eloquent model, you may pass the model itself. The ID will be extracted automatically:

```php
// For a route with the following URI: /profile/{id}

return redirect()->route('profile', [$user]);
```

If you would like to customize the value that is placed in the route parameter, you can specify the column in the route parameter definition (`/profile/{id:slug}`) or you can override the `getRouteKey` method on your Eloquent model:

```php
/**
 * Get the value of the model's route key.
 */
public function getRouteKey(): mixed
{
    return $this->slug;
}
```

---

## Components Input > Icon buttons
Components Input | livewire/flux@2.x | Source: https://fluxui.dev/components/input#livewire/flux

Icon buttons

Append a button to the inside of an input to provide associated functionality.

<flux:input placeholder="Search orders">    <x-slot name="iconTrailing">        <flux:button size="sm" variant="subtle" icon="x-mark" class="-mr-1" />    </x-slot></flux:input><flux:input type="password" value="password">    <x-slot name="iconTrailing">        <flux:button size="sm" variant="subtle" icon="eye" class="-mr-1" />    </x-slot></flux:input>

---

## Eloquent Relationships Aggregating Related Models Counting Related Models
Eloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md

### Counting Related Models

Sometimes you may want to count the number of related models for a given relationship without actually loading the models. To accomplish this, you may use the `withCount` method. The `withCount` method will place a `{relation}_count` attribute on the resulting models:

```php
use App\Models\Post;

$posts = Post::withCount('comments')->get();

foreach ($posts as $post) {
    echo $post->comments_count;
}
```

By passing an array to the `withCount` method, you may add the "counts" for multiple relations as well as add additional constraints to the queries:

```php
use Illuminate\Database\Eloquent\Builder;

$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
    $query->where('content', 'like', 'code%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
```

You may also alias the relationship count result, allowing multiple counts on the same relationship:

```php
use Illuminate\Database\Eloquent\Builder;

$posts = Post::withCount([
    'comments',
    'comments as pending_comments_count' => function (Builder $query) {
        $query->where('approved', false);
    },
])->get();

echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
```

---

## Components Otp Input > Example usage
Components Otp Input | livewire/flux@2.x | Source: https://fluxui.dev/components/otp-input#livewire/flux

Example usage

Display a form for verifying one-time passwords.

<flux:card>    <form wire:submit="verify" class="space-y-8">
            <flux:heading size="lg" class="text-center">Verify your account</flux:heading>            <flux:text class="text-center">Please enter a one-time password from the authenticator app.</flux:text>
        <flux:otp wire:model="code" length="6" label="OTP Code" label:sr-only :error:icon="false" error:class="text-center" class="mx-auto" />
            <flux:button variant="primary" type="submit" class="w-full">Verify</flux:button>            <flux:button wire:click="resend" class="w-full">Resend code</flux:button>
    </form></flux:card>

---

## Components Select > flux:select.input
Components Select | livewire/flux@2.x | Source: https://fluxui.dev/components/select#livewire/flux

flux:select.input

<ui-table-scroll-area>

Prop

Description

    placeholder

Text displayed when  no option is selected.

    invalid

Applies error styling to the input.

    size

Size of the input. Options: sm, xs.

</ui-table-scroll-area>

---

## Forms Extracting input fields to Blade components
Forms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms

## Extracting input fields to Blade components

Even in a small component such as the `post.create` example we've been discussing, we end up duplicating lots of form field boilerplate like validation messages and labels.

It can be helpful to extract repetitive UI elements such as these into dedicated [Blade components](https://laravel.com/docs/blade#components) to be shared across your application.

For example, below is the original Blade template from the `post.create` component. We will be extracting the following two text inputs into dedicated Blade components:

```blade
<form wire:submit="save">
    <input type="text" wire:model="title"> <!-- [tl! highlight:3] -->
    <div>
        @error('title') <span class="error">{{ $message }}</span> @enderror
    </div>

    <input type="text" wire:model="content"> <!-- [tl! highlight:3] -->
    <div>
        @error('content') <span class="error">{{ $message }}</span> @enderror
    </div>

    <button type="submit">Save</button>
</form>
```

Here's what the template will look like after extracting a re-usable Blade component called `<x-input-text>`:

```blade
<form wire:submit="save">
    <x-input-text name="title" wire:model="title" /> <!-- [tl! highlight] -->

    <x-input-text name="content" wire:model="content" /> <!-- [tl! highlight] -->

    <button type="submit">Save</button>
</form>
```

Next, here's the source for the `x-input-text` component:

```blade
<!-- resources/views/components/input-text.blade.php -->

@props(['name'])

<input type="text" name="{{ $name }}" {{ $attributes }}>

<div>
    @error($name) <span class="error">{{ $message }}</span> @enderror
</div>
```

As you can see, we took the repetitive HTML and placed it inside a dedicated Blade component.

For the most part, the Blade component contains only the extracted HTML from the original component. However, we have added two things:

* The `@props` directive
* The `{{ $attributes }}` statement on the input

Let's discuss each of these additions:

By specifying `name` as a "prop" using `@props(['name'])` we are telling Blade: if an attribute called "name" is set on this component, take its value and make it available inside this component as `$name`.

For other attributes that don't have an explicit purpose, we used the `{{ $attributes }}` statement. This is used for "attribute forwarding", or in other words, taking any HTML attributes written on the Blade component and forwarding them onto an element within the component.

This ensures `wire:model="title"` and any other extra attributes such as `disabled`, `class="..."`, or `required` still get forwarded to the actual `<input>` element.

### Custom form controls

In the previous example, we "wrapped" an input element into a re-usable Blade component we can use as if it was a native HTML input element.

This pattern is very useful; however, there might be some cases where you want to create an entire input component from scratch (without an underlying native input element), but still be able to bind its value to Livewire properties using `wire:model`.

For example, let's imagine you wanted to create an `<x-input-counter />` component that was a simple "counter" input written in Alpine.

Before we create a Blade component, let's first look at a simple, pure-Alpine, "counter" component for reference:

```blade
<div x-data="{ count: 0 }">
    <button x-on:click="count--">-</button>

    <span x-text="count"></span>

    <button x-on:click="count++">+</button>
</div>
```

As you can see, the component above shows a number alongside two buttons to increment and decrement that number.

Now, let's imagine we want to extract this component into a Blade component called `<x-input-counter />` that we would use within a component like so:

```blade
<x-input-counter wire:model="quantity" />
```

Creating this component is mostly simple. We take the HTML of the counter and place it inside a Blade component template like `resources/views/components/input-counter.blade.php`.

However, making it work with `wire:model="quantity"` so that you can easily bind data from your Livewire component to the "count" inside this Alpine component needs one extra step.

Here's the source for the component:

```blade
<!-- resources/view/components/input-counter.blade.php -->

<div x-data="{ count: 0 }" x-modelable="count" {{ $attributes}}>
    <button x-on:click="count--">-</button>

    <span x-text="count"></span>

    <button x-on:click="count++">+</button>
</div>
```

As you can see, the only different bit about this HTML is the `x-modelable="count"` and `{{ $attributes }}`.

`x-modelable` is a utility in Alpine that tells Alpine to make a certain piece of data available for binding from outside. [The Alpine documentation has more information on this directive.](https://alpinejs.dev/directives/modelable)

`{{ $attributes }}`, as we explored earlier, forwards any attributes passed into the Blade component from outside. In this case, the `wire:model` directive will be forwarded.

Because of `{{ $attributes }}`, when the HTML is rendered in the browser, `wire:model="quantity"` will be rendered alongside `x-modelable="count"` on the root `<div>` of the Alpine component like so:

```blade
<div x-data="{ count: 0 }" x-modelable="count" wire:model="quantity">
```

`x-modelable="count"` tells Alpine to look for any `x-model` or `wire:model` statements and use "count" as the data to bind them to.

Because `x-modelable` works for both `wire:model` and `x-model`, you can also use this Blade component interchangeably with Livewire and Alpine. Here’s an example of using this Blade component in a purely Alpine context:

```blade
<x-input-counter x-model="quantity" />
```

Creating custom input elements in your application is extremely powerful but requires a deeper understanding of the utilities Livewire and Alpine provide and how they interact with each other.

_[Truncated]_


Output:
Wall time: 3.8720 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Components Pagination > Large result set\nComponents Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux\n\n
Large result set

When working with large result sets, the pagination component automatically adapts to show a reasonable number of page links. It shows the first and last pages, along with a window of pages around the current page, and adds ellipses for any gaps to ensure efficient navigation through numerous pages.

<flux:pagination :paginator=\"$orders\" />

\n\n---\n\n## Components Field > With badge\nComponents Field | livewire/flux@2.x | Source: https://fluxui.dev/components/field#livewire/flux\n\n
With badge

Badges allow you to enhance a field with additional information such as being \"required\" or \"optional\" when it might not be expected.

<flux:field>    <flux:label badge=\"Required\">Email</flux:label>    <flux:input type=\"email\" required />    <flux:error name=\"email\" /></flux:field><flux:field>    <flux:label badge=\"Optional\">Phone number</flux:label>    <flux:input type=\"phone\" placeholder=\"(555) 555-5555\" mask=\"(999) 999-9999\"  />    <flux:error name=\"phone\" /></flux:field>

\n\n---\n\n## Urls URLs for Named Routes Eloquent Models\nUrls | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/urls.md\n\n\n#### Eloquent Models\n\nYou will often be generating URLs using the route key (typically the primary key) of [Eloquent models](/docs/{{version}}/eloquent). For this reason, you may pass Eloquent models as parameter values. The `route` helper will automatically extract the model's route key:\n\n```php\necho route('post.show', ['post' => $post]);\n```\n\n---\n\n## Understanding Nesting Every component is independent {#every-component-is-an-island}\nUnderstanding Nesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/understanding-nesting\n\n## Every component is independent {#every-component-is-an-island}\n\nIn Livewire, every component on a page tracks its state and makes updates independently of other components.\n\nFor example, consider the following `Posts` and nested `ShowPost` components:\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Livewire\\Component;\n\nclass Posts extends Component\n{\n    public $postLimit = 2;\n\n    public function render()\n    {\n        return view('livewire.posts', [\n            'posts' => Auth::user()->posts()\n                ->limit($this->postLimit)->get(),\n        ]);\n    }\n}\n```\n\n```blade\n<div>\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    @foreach ($posts as $post)\n        <livewire:show-post :$post :wire:key=\"$post->id\">\n    @endforeach\n</div>\n```\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nclass ShowPost extends Component\n{\n    public Post $post;\n\n    public function render()\n    {\n        return view('livewire.show-post');\n    }\n}\n```\n\n```blade\n<div>\n    <h1>{{ $post->title }}</h1>\n\n    <p>{{ $post->content }}</p>\n\n    <button wire:click=\"$refresh\">Refresh post</button>\n</div>\n```\n\nHere's what the HTML for the entire component tree might look like on initial page load:\n\n```html\n<div wire:id=\"123\" wire:snapshot=\"...\">\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    <div wire:id=\"456\" wire:snapshot=\"...\">\n        <h1>The first post</h1>\n\n        <p>Post content</p>\n\n        <button wire:click=\"$refresh\">Refresh post</button>\n    </div>\n\n    <div wire:id=\"789\" wire:snapshot=\"...\">\n        <h1>The second post</h1>\n\n        <p>Post content</p>\n\n        <button wire:click=\"$refresh\">Refresh post</button>\n    </div>\n</div>\n```\n\nNotice that the parent component contains both its rendered template and the rendered templates of all the components nested within it.\n\nBecause each component is independent, they each have their own IDs and snapshots (`wire:id` and `wire:snapshot`) embedded in their HTML for Livewire's JavaScript core to extract and track.\n\nLet's consider a few different update scenarios to see the differences in how Livewire handles different levels of nesting.\n\n### Updating a child\n\nIf you were to click the \"Refresh post\" button in one of the child `show-post` components, here is what would be sent to the server:\n\n```js\n{\n    memo: { name: 'show-post', id: '456' },\n\n    state: { ... },\n}\n```\n\nHere's the HTML that would get sent back:\n\n```html\n<div wire:id=\"456\">\n    <h1>The first post</h1>\n\n    <p>Post content</p>\n\n    <button wire:click=\"$refresh\">Refresh post</button>\n</div>\n```\n\nThe important thing to note here is that when an update is triggered on a child component, only that component's data is sent to the server, and only that component is re-rendered.\n\nNow let's look at the less intuitive scenario: updating a parent component.\n\n### Updating the parent\n\nAs a reminder, here's the Blade template of the parent `Posts` component:\n\n```blade\n<div>\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    @foreach ($posts as $post)\n        <livewire:show-post :$post :wire:key=\"$post->id\">\n    @endforeach\n</div>\n```\n\nIf a user changes the \"Post Limit\" value from `2` to `1`, an update will be solely triggered on the parent.\n\nHere's an example of what the request payload might look like:\n\n```js\n{\n    updates: { postLimit: 1 },\n\n    snapshot: {\n        memo: { name: 'posts', id: '123' },\n\n        state: { postLimit: 2, ... },\n    },\n}\n```\n\nAs you can see, only the snapshot for the parent `Posts` component is sent along to the server.\n\nAn important question you might be asking yourself is: what happens when the parent component re-renders and encounters the child `show-post` components? How will it re-render the children if their snapshots haven't been included in the request payload?\n\nThe answer is: they won't be re-rendered.\n\nWhen Livewire renders the `Posts` component, it will render placeholders for any child components it encounters.\n\nHere is an example of what the rendered HTML for the `Posts` component might be after the above update:\n\n```html\n<div wire:id=\"123\">\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    <div wire:id=\"456\"></div>\n</div>\n```\n\nAs you can see, only one child has been rendered because `postLimit` was updated to `1`. However, you will also notice that instead of the full child component, there is only an empty `<div></div>` with the matching `wire:id` attribute.\n\nWhen this HTML is received on the frontend, Livewire will _morph_ the old HTML for this component into this new HTML, but intelligently skip any child component placeholders.\n\nThe effect is that, after _morphing_, the final DOM content of the parent `Posts` component will be the following:\n\n```html\n<div wire:id=\"123\">\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    <div wire:id=\"456\">\n        <h1>The first post</h1>\n\n        <p>Post content</p>\n\n        <button wire:click=\"$refresh\">Refresh post</button>\n    </div>\n</div>\n```\n\n---\n\n## Testing Configuring Pest for view-based components\nTesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing\n\n## Configuring Pest for view-based components\n\nIf you're writing tests alongside your view-based components (single-file or multi-file), you'll need to configure Pest to recognize these test files.\n\nFirst, update your `tests/Pest.php` file to include the `resources/views` directory:\n\n```php\npest()->extend(Tests\\TestCase::class)\n    // ...\n    ->in('Feature', '../resources/views');\n```\n\nThis tells Pest to use your `TestCase` base class for tests found in both the `tests/Feature` directory and anywhere within `resources/views`.\n\nNext, update your `phpunit.xml` file to include a test suite for component tests:\n\n```xml\n<testsuite name=\"Components\">\n    <directory suffix=\".test.php\">resources/views</directory>\n</testsuite>\n```\n\nNow Pest will recognize and run tests located next to your components when you run `./vendor/bin/pest`.\n\n---\n\n## Components Table > Pagination\nComponents Table | livewire/flux@2.x | Source: https://fluxui.dev/components/table#livewire/flux\n\n
Pagination

Allow users to navigate through different pages of data by passing in any model paginator to the paginate prop.

<flux:table :paginate=\"$orders\">    </flux:table>

\n\n---\n\n## Wire Model Input fields\nWire Model | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-model\n\n## Input fields\n\nLivewire supports most native input elements out of the box. Meaning you should just be able to attach `wire:model` to any input element in the browser and easily bind properties to them.\n\nHere's a comprehensive list of the different available input types and how you use them in a Livewire context.\n\n### Text inputs\n\nFirst and foremost, text inputs are the bedrock of most forms. Here's how to bind a property named \"title\" to one:\n\n```blade\n<input type=\"text\" wire:model=\"title\">\n```\n\n### Textarea inputs\n\nTextarea elements are similarly straightforward. Simply add `wire:model` to a textarea and the value will be bound:\n\n```blade\n<textarea type=\"text\" wire:model=\"content\"></textarea>\n```\n\nIf the \"content\" value is initialized with a string, Livewire will fill the textarea with that value - there's no need to do something like the following:\n\n```blade\n<!-- Warning: This snippet demonstrates what NOT to do... -->\n\n<textarea type=\"text\" wire:model=\"content\">{{ $content }}</textarea>\n```\n\n### Checkboxes\n\nCheckboxes can be used for single values, such as when toggling a boolean property. Or, checkboxes may be used to toggle a single value in a group of related values. We'll discuss both scenarios:\n\n#### Single checkbox\n\nAt the end of a signup form, you might have a checkbox allowing the user to opt-in to email updates. You might call this property `$receiveUpdates`. You can easily bind this value to the checkbox using `wire:model`:\n\n```blade\n<input type=\"checkbox\" wire:model=\"receiveUpdates\">\n```\n\nNow when the `$receiveUpdates` value is `false`, the checkbox will be unchecked. Of course, when the value is `true`, the checkbox will be checked.\n\n#### Multiple checkboxes\n\nNow, let's say in addition to allowing the user to decide to receive updates, you have an array property in your class called `$updateTypes`, allowing the user to choose from a variety of update types:\n\n```php\npublic $updateTypes = [];\n```\n\nBy binding multiple checkboxes to the `$updateTypes` property, the user can select multiple update types and they will be added to the `$updateTypes` array property:\n\n```blade\n<input type=\"checkbox\" value=\"email\" wire:model=\"updateTypes\">\n<input type=\"checkbox\" value=\"sms\" wire:model=\"updateTypes\">\n<input type=\"checkbox\" value=\"notification\" wire:model=\"updateTypes\">\n```\n\nFor example, if the user checks the first two boxes but not the third, the value of `$updateTypes` will be: `[\"email\", \"sms\"]`\n\n### Radio buttons\n\nTo toggle between two different values for a single property, you may use radio buttons:\n\n```blade\n<input type=\"radio\" value=\"yes\" wire:model=\"receiveUpdates\">\n<input type=\"radio\" value=\"no\" wire:model=\"receiveUpdates\">\n```\n\n### Select dropdowns\n\nLivewire makes it simple to work with `<select>` dropdowns. When adding `wire:model` to a dropdown, the currently selected value will be bound to the provided property name and vice versa.\n\nIn addition, there's no need to manually add `selected` to the option that will be selected - Livewire handles that for you automatically.\n\nBelow is an example of a select dropdown filled with a static list of states:\n\n```blade\n<select wire:model=\"state\">\n    <option value=\"AL\">Alabama</option>\n    <option value=\"AK\">Alaska</option>\n    <option value=\"AZ\">Arizona</option>\n    ...\n</select>\n```\n\nWhen a specific state is selected, for example, \"Alaska\", the `$state` property on the component will be set to `AK`. If you would prefer the value to be set to \"Alaska\" instead of \"AK\", you can leave the `value=\"\"` attribute off the `<option>` element entirely.\n\nOften, you may build your dropdown options dynamically using Blade:\n\n```blade\n<select wire:model=\"state\">\n    @foreach (\\App\\Models\\State::all() as $state)\n        <option value=\"{{ $state->id }}\">{{ $state->label }}</option>\n    @endforeach\n</select>\n```\n\nIf you don't have a specific option selected by default, you may want to show a muted placeholder option by default, such as \"Select a state\":\n\n```blade\n<select wire:model=\"state\">\n    <option disabled value=\"\">Select a state...</option>\n\n    @foreach (\\App\\Models\\State::all() as $state)\n        <option value=\"{{ $state->id }}\">{{ $state->label }}</option>\n    @endforeach\n</select>\n```\n\nAs you can see, there is no \"placeholder\" attribute for a select menu like there is for text inputs. Instead, you have to add a `disabled` option element as the first option in the list.\n\n### Dependent select dropdowns\n\nSometimes you may want one select menu to be dependent on another. For example, a list of cities that changes based on which state is selected.\n\nFor the most part, this works as you'd expect, however there is one important gotcha: You must add a `wire:key` to the changing select so that Livewire properly refreshes its value when the options change.\n\nHere's an example of two selects, one for states, one for cities. When the state select changes, the options in the city select will change properly:\n\n```blade\n<!-- States select menu... -->\n<select wire:model.live=\"selectedState\">\n    @foreach (State::all() as $state)\n        <option value=\"{{ $state->id }}\">{{ $state->label }}</option>\n    @endforeach\n</select>\n\n<!-- Cities dependent select menu... -->\n<select wire:model.live=\"selectedCity\" wire:key=\"{{ $selectedState }}\"> <!-- [tl! highlight] -->\n    @foreach (City::whereStateId($selectedState->id)->get() as $city)\n        <option value=\"{{ $city->id }}\">{{ $city->label }}</option>\n    @endforeach\n</select>\n```\n\nAgain, the only thing non-standard here is the `wire:key` that has been added to the second select. This ensures that when the state changes, the \"selectedCity\" value will be reset properly.\n\n### Multi-select dropdowns\n\nIf you are using a \"multiple\" select menu, Livewire works as expected. In this example, states will be added to the `$states` array property when they are selected and removed if they are deselected:\n\n```blade\n<select wire:model=\"states\" multiple>\n    <option value=\"AL\">Alabama</option>\n    <option value=\"AK\">Alaska</option>\n    <option value=\"AZ\">Arizona</option>\n    ...\n</select>\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Eloquent Models and UUIDv7\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Models and UUIDv7\n\n**Likelihood Of Impact: Medium**\n\nThe `HasUuids` trait now returns UUIDs that are compatible with version 7 of the UUID spec (ordered UUIDs). If you would like to continue using ordered UUIDv4 strings for your model's IDs, you should now use the `HasVersion4Uuids` trait:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Concerns\\HasUuids; // [tl! remove]\nuse Illuminate\\Database\\Eloquent\\Concerns\\HasVersion4Uuids as HasUuids; // [tl! add]\n```\n\nThe `HasVersion7Uuids` trait has been removed. If you were previously using this trait, you should use the `HasUuids` trait instead, which now provides the same behavior.\n\n---\n\n## Troubleshooting Component mismatches\nTroubleshooting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/troubleshooting\n\n## Component mismatches\n\nWhen interacting with Livewire components on your page, you may encounter odd behavior or error messages like the following:\n\n```\nError: Component already initialized\n```\n\n```\nError: Snapshot missing on Livewire component with id: ...\n```\n\nThere are lots of reasons why you may encounter these messages, but the most common one is forgetting to add `wire:key` to elements and components inside a `@foreach` loop.\n\n### Adding `wire:key`\n\nAny time you have a loop or switch case in your Blade templates using something like `@foreach`, or `@switch`/`@case`, you need to add `wire:key` to the opening tag of the first element:\n\n```blade\n@foreach($posts as $post)\n    <div wire:key=\"{{ $post->id }}\"> <!-- [tl! highlight] -->\n        ...\n    </div>\n@endforeach\n```\n\nThis ensures that Livewire can keep track of different elements in the loop or conditional when it changes.\n\nThe same applies to Livewire components within a loop:\n\n```blade\n@foreach($posts as $post)\n    <livewire:show-post :$post :wire:key=\"$post->id\" /> <!-- [tl! highlight] -->\n@endforeach\n```\n\nHowever, here's a tricky scenario you might not have assumed:\n\nWhen you have a Livewire component deeply nested inside a `@foreach` loop, you STILL need to add a key to it. For example:\n\n```blade\n@foreach($posts as $post)\n    <div wire:key=\"{{ $post->id }}\">\n        ...\n        <livewire:show-post :$post :wire:key=\"$post->id\" /> <!-- [tl! highlight] -->\n        ...\n    </div>\n@endforeach\n```\n\nWithout the key on the nested Livewire component, Livewire will be unable to match the looped components up between network requests.\n\n#### Prefixing keys\n\nAnother tricky scenario you may run into is having duplicate keys within the same component. This often results from using model IDs as keys, which can sometimes collide.\n\nHere's an example where we need to add a `post-` and an `author-` prefix to designate each set of keys as unique. Otherwise, if you have a `$post` and `$author` model with the same ID, you would have an ID collision:\n\n```blade\n<div>\n    @foreach($posts as $post)\n        <div wire:key=\"post-{{ $post->id }}\">...</div> <!-- [tl! highlight] -->\n    @endforeach\n\n    @foreach($authors as $author)\n        <div wire:key=\"author-{{ $author->id }}\">...</div> <!-- [tl! highlight] -->\n    @endforeach\n</div>\n```\n\n---\n\n## Components Pagination > Simple paginator\nComponents Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux\n\n
Simple paginator

Use the simple paginator when working with large datasets where counting the total number of results would be expensive. The simple paginator provides \"Previous\" and \"Next\" buttons without displaying the total number of pages or records.

<flux:pagination :paginator=\"$orders\" />

\n\n---\n\n## Eloquent Relationships Aggregating Related Models\nEloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md\n\n\n## Aggregating Related Models\n\n---\n\n## Components Pagination > Pagination\nComponents Pagination | livewire/flux@2.x | Source: https://fluxui.dev/components/pagination#livewire/flux\n\n

\n    Pagination\n

Display a series of buttons to navigate through a list of items.

<flux:pagination :paginator=\"$orders\" />

\n\n---\n\n## Wire Confirm Prompting users for input\nWire Confirm | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-confirm\n\n## Prompting users for input\n\nFor even more dangerous actions such as deleting a user's account entirely, you may want to present them with a confirmation prompt which they would need to type in a specific string of characters to confirm the action.\n\nLivewire provides a helpful `.prompt` modifier, that when applied to `wire:confirm`, it will prompt the user for input and only confirm the action if the input matches (case-sensitive) the provided string (designated by a \"|\" (pipe) character at the end if the `wire:confirm` value):\n\n```blade\n<button\n    type=\"button\"\n    wire:click=\"delete\"\n    wire:confirm.prompt=\"Are you sure?\\n\\nType DELETE to confirm|DELETE\"\n>\n    Delete account <!-- [tl! highlight:-2,1] -->\n</button>\n```\n\nWhen a user presses \"Delete account\", the action will only be performed if \"DELETE\" is entered into the prompt, otherwise, the action will be cancelled.\n\n---\n\n## Scout Searching Where Clauses Customizing the Eloquent Results Query\nScout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md\n\n\n#### Customizing the Eloquent Results Query\n\nAfter Scout retrieves a list of matching Eloquent models from your application's search engine, Eloquent is used to retrieve all of the matching models by their primary keys. You may customize this query by invoking the `query` method. The `query` method accepts a closure that will receive the Eloquent query builder instance as an argument:\n\n```php\nuse App\\Models\\Order;\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n$orders = Order::search('Star Trek')\n    ->query(fn (Builder $query) => $query->with('invoices'))\n    ->get();\n```\n\nWhen using a third-party engine, this callback is invoked after the relevant models have already been retrieved from the search engine, so it should not be used for \"filtering\" results — use [Scout where clauses](#where-clauses) instead. However, when using the database engine, the `query` method's constraints are applied directly to the database query, so you may use it for filtering as well.\n\n---\n\n## Redirecting Redirecting to full-page components\nRedirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting\n\n## Redirecting to full-page components\n\nBecause Livewire uses Laravel's built-in redirection feature, you can use all of the redirection methods available to you in a typical Laravel application.\n\nFor example, if you are using a Livewire component as a full-page component for a route like so:\n\n```php\nRoute::livewire('/posts', 'pages::show-posts');\n```\n\nYou can redirect to it simply by using the route path:\n\n```php\npublic function save()\n{\n    // ...\n\n    $this->redirect('/posts');\n}\n```\n\n---\n\n## Forms Throttling input\nForms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms\n\n## Throttling input\n\nAs stated previously, when an input debounce is applied to a field, a network request will not be sent until the user has stopped typing for a certain amount of time. This means if the user continues typing a long message, a network request won't be sent until the user is finished.\n\nSometimes this isn't the desired behavior, and you would rather send a request as the user types, not when they've finished or taken a break.\n\nIn these cases, you can instead use `.throttle` to signify a time interval to send network requests:\n\n```blade\n<input type=\"text\" wire:model.live.throttle.150ms=\"title\" >\n```\n\nIn the above example, as a user is typing continuously in the \"title\" field, a network request will be sent every 150 milliseconds until the user is finished.\n\n---\n\n## Styles When to use component styles\nStyles | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/styles\n\n## When to use component styles\n\n**Use scoped styles when:**\n- Styling is specific to a single component\n- You want to avoid CSS class name collisions\n- You're building reusable, self-contained components\n\n**Use global styles when:**\n- You need to style elements outside your component\n- You're defining utility classes used across multiple components\n- You're overriding third-party library styles\n\n**Use `@assets` for external stylesheets:**\n- When loading CSS from a CDN\n- When including third-party library styles\n\n```blade\n@assets\n<link rel=\"stylesheet\" href=\"https://cdn.example.com/library.css\">\n@endassets\n```\n\n---\n\n## Lazy Full-page lazy loading\nLazy | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/lazy\n\n## Full-page lazy loading\n\nYou can lazy load or defer full-page Livewire components using route methods.\n\n### Lazy loading full pages\n\nUse `->lazy()` to load the component when it enters the viewport:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->lazy();\n```\n\n### Deferring full pages\n\nUse `->defer()` to load the component immediately after the page loads:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->defer();\n```\n\n### Disabling lazy/defer loading\n\nIf a component is lazy or deferred by default (via the `#[Lazy]` or `#[Defer]` attribute), you can opt-out using `enabled: false`:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->lazy(enabled: false);\nRoute::livewire('/dashboard', 'pages::dashboard')->defer(enabled: false);\n```\n\n---\n\n## Responses Redirects Redirecting to Named Routes Populating Parameters via Eloquent Models\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n#### Populating Parameters via Eloquent Models\n\nIf you are redirecting to a route with an \"ID\" parameter that is being populated from an Eloquent model, you may pass the model itself. The ID will be extracted automatically:\n\n```php\n// For a route with the following URI: /profile/{id}\n\nreturn redirect()->route('profile', [$user]);\n```\n\nIf you would like to customize the value that is placed in the route parameter, you can specify the column in the route parameter definition (`/profile/{id:slug}`) or you can override the `getRouteKey` method on your Eloquent model:\n\n```php\n/**\n * Get the value of the model's route key.\n */\npublic function getRouteKey(): mixed\n{\n    return $this->slug;\n}\n```\n\n---\n\n## Components Input > Icon buttons\nComponents Input | livewire/flux@2.x | Source: https://fluxui.dev/components/input#livewire/flux\n\n
Icon buttons

Append a button to the inside of an input to provide associated functionality.

<flux:input placeholder=\"Search orders\">    <x-slot name=\"iconTrailing\">        <flux:button size=\"sm\" variant=\"subtle\" icon=\"x-mark\" class=\"-mr-1\" />    </x-slot></flux:input><flux:input type=\"password\" value=\"password\">    <x-slot name=\"iconTrailing\">        <flux:button size=\"sm\" variant=\"subtle\" icon=\"eye\" class=\"-mr-1\" />    </x-slot></flux:input>

\n\n---\n\n## Eloquent Relationships Aggregating Related Models Counting Related Models\nEloquent Relationships | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-relationships.md\n\n\n### Counting Related Models\n\nSometimes you may want to count the number of related models for a given relationship without actually loading the models. To accomplish this, you may use the `withCount` method. The `withCount` method will place a `{relation}_count` attribute on the resulting models:\n\n```php\nuse App\\Models\\Post;\n\n$posts = Post::withCount('comments')->get();\n\nforeach ($posts as $post) {\n    echo $post->comments_count;\n}\n```\n\nBy passing an array to the `withCount` method, you may add the \"counts\" for multiple relations as well as add additional constraints to the queries:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {\n    $query->where('content', 'like', 'code%');\n}])->get();\n\necho $posts[0]->votes_count;\necho $posts[0]->comments_count;\n```\n\nYou may also alias the relationship count result, allowing multiple counts on the same relationship:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n$posts = Post::withCount([\n    'comments',\n    'comments as pending_comments_count' => function (Builder $query) {\n        $query->where('approved', false);\n    },\n])->get();\n\necho $posts[0]->comments_count;\necho $posts[0]->pending_comments_count;\n```\n\n---\n\n## Components Otp Input > Example usage\nComponents Otp Input | livewire/flux@2.x | Source: https://fluxui.dev/components/otp-input#livewire/flux\n\n
Example usage

Display a form for verifying one-time passwords.

<flux:card>    <form wire:submit=\"verify\" class=\"space-y-8\">
            <flux:heading size=\"lg\" class=\"text-center\">Verify your account</flux:heading>            <flux:text class=\"text-center\">Please enter a one-time password from the authenticator app.</flux:text>
        <flux:otp wire:model=\"code\" length=\"6\" label=\"OTP Code\" label:sr-only :error:icon=\"false\" error:class=\"text-center\" class=\"mx-auto\" />
            <flux:button variant=\"primary\" type=\"submit\" class=\"w-full\">Verify</flux:button>            <flux:button wire:click=\"resend\" class=\"w-full\">Resend code</flux:button>
    </form></flux:card>

\n\n---\n\n## Components Select > flux:select.input\nComponents Select | livewire/flux@2.x | Source: https://fluxui.dev/components/select#livewire/flux\n\n
flux:select.input

<ui-table-scroll-area>

Prop

Description

\n    placeholder\n

Text displayed when  no option is selected.

\n    invalid\n

Applies error styling to the input.

\n    size\n

Size of the input. Options: sm, xs.

</ui-table-scroll-area>

\n\n---\n\n## Forms Extracting input fields to Blade components\nForms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms\n\n## Extracting input fields to Blade components\n\nEven in a small component such as the `post.create` example we've been discussing, we end up duplicating lots of form field boilerplate like validation messages and labels.\n\nIt can be helpful to extract repetitive UI elements such as these into dedicated [Blade components](https://laravel.com/docs/blade#components) to be shared across your application.\n\nFor example, below is the original Blade template from the `post.create` component. We will be extracting the following two text inputs into dedicated Blade components:\n\n```blade\n<form wire:submit=\"save\">\n    <input type=\"text\" wire:model=\"title\"> <!-- [tl! highlight:3] -->\n    <div>\n        @error('title') <span class=\"error\">{{ $message }}</span> @enderror\n    </div>\n\n    <input type=\"text\" wire:model=\"content\"> <!-- [tl! highlight:3] -->\n    <div>\n        @error('content') <span class=\"error\">{{ $message }}</span> @enderror\n    </div>\n\n    <button type=\"submit\">Save</button>\n</form>\n```\n\nHere's what the template will look like after extracting a re-usable Blade component called `<x-input-text>`:\n\n```blade\n<form wire:submit=\"save\">\n    <x-input-text name=\"title\" wire:model=\"title\" /> <!-- [tl! highlight] -->\n\n    <x-input-text name=\"content\" wire:model=\"content\" /> <!-- [tl! highlight] -->\n\n    <button type=\"submit\">Save</button>\n</form>\n```\n\nNext, here's the source for the `x-input-text` component:\n\n```blade\n<!-- resources/views/components/input-text.blade.php -->\n\n@props(['name'])\n\n<input type=\"text\" name=\"{{ $name }}\" {{ $attributes }}>\n\n<div>\n    @error($name) <span class=\"error\">{{ $message }}</span> @enderror\n</div>\n```\n\nAs you can see, we took the repetitive HTML and placed it inside a dedicated Blade component.\n\nFor the most part, the Blade component contains only the extracted HTML from the original component. However, we have added two things:\n\n* The `@props` directive\n* The `{{ $attributes }}` statement on the input\n\nLet's discuss each of these additions:\n\nBy specifying `name` as a \"prop\" using `@props(['name'])` we are telling Blade: if an attribute called \"name\" is set on this component, take its value and make it available inside this component as `$name`.\n\nFor other attributes that don't have an explicit purpose, we used the `{{ $attributes }}` statement. This is used for \"attribute forwarding\", or in other words, taking any HTML attributes written on the Blade component and forwarding them onto an element within the component.\n\nThis ensures `wire:model=\"title\"` and any other extra attributes such as `disabled`, `class=\"...\"`, or `required` still get forwarded to the actual `<input>` element.\n\n### Custom form controls\n\nIn the previous example, we \"wrapped\" an input element into a re-usable Blade component we can use as if it was a native HTML input element.\n\nThis pattern is very useful; however, there might be some cases where you want to create an entire input component from scratch (without an underlying native input element), but still be able to bind its value to Livewire properties using `wire:model`.\n\nFor example, let's imagine you wanted to create an `<x-input-counter />` component that was a simple \"counter\" input written in Alpine.\n\nBefore we create a Blade component, let's first look at a simple, pure-Alpine, \"counter\" component for reference:\n\n```blade\n<div x-data=\"{ count: 0 }\">\n    <button x-on:click=\"count--\">-</button>\n\n    <span x-text=\"count\"></span>\n\n    <button x-on:click=\"count++\">+</button>\n</div>\n```\n\nAs you can see, the component above shows a number alongside two buttons to increment and decrement that number.\n\nNow, let's imagine we want to extract this component into a Blade component called `<x-input-counter />` that we would use within a component like so:\n\n```blade\n<x-input-counter wire:model=\"quantity\" />\n```\n\nCreating this component is mostly simple. We take the HTML of the counter and place it inside a Blade component template like `resources/views/components/input-counter.blade.php`.\n\nHowever, making it work with `wire:model=\"quantity\"` so that you can easily bind data from your Livewire component to the \"count\" inside this Alpine component needs one extra step.\n\nHere's the source for the component:\n\n```blade\n<!-- resources/view/components/input-counter.blade.php -->\n\n<div x-data=\"{ count: 0 }\" x-modelable=\"count\" {{ $attributes}}>\n    <button x-on:click=\"count--\">-</button>\n\n    <span x-text=\"count\"></span>\n\n    <button x-on:click=\"count++\">+</button>\n</div>\n```\n\nAs you can see, the only different bit about this HTML is the `x-modelable=\"count\"` and `{{ $attributes }}`.\n\n`x-modelable` is a utility in Alpine that tells Alpine to make a certain piece of data available for binding from outside. [The Alpine documentation has more information on this directive.](https://alpinejs.dev/directives/modelable)\n\n`{{ $attributes }}`, as we explored earlier, forwards any attributes passed into the Blade component from outside. In this case, the `wire:model` directive will be forwarded.\n\nBecause of `{{ $attributes }}`, when the HTML is rendered in the browser, `wire:model=\"quantity\"` will be rendered alongside `x-modelable=\"count\"` on the root `<div>` of the Alpine component like so:\n\n```blade\n<div x-data=\"{ count: 0 }\" x-modelable=\"count\" wire:model=\"quantity\">\n```\n\n`x-modelable=\"count\"` tells Alpine to look for any `x-model` or `wire:model` statements and use \"count\" as the data to bind them to.\n\nBecause `x-modelable` works for both `wire:model` and `x-model`, you can also use this Blade component interchangeably with Livewire and Alpine. Here’s an example of using this Blade component in a purely Alpine context:\n\n```blade\n<x-input-counter x-model=\"quantity\" />\n```\n\nCreating custom input elements in your application is extremely powerful but requires a deeper understanding of the utilities Livewire and Alpine provide and how they interact with each other.\n\n_[Truncated]_"}]


sed -n '1,220p' database/migrations/2026_05_03_211556_create_customers_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('customers', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('email');
            $table->string('password_hash')->nullable();
            $table->string('name')->nullable();
            $table->boolean('marketing_opt_in')->default(false);
            $table->timestamps();

            $table->unique(['store_id', 'email']);
            $table->index('store_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('customers');
    }
};


sed -n '1,220p' database/factories/CustomerFactory.php

<?php

namespace Database\Factories;

use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Customer>
 */
class CustomerFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'store_id' => Store::factory(),
            'email' => fake()->unique()->safeEmail(),
            'password' => 'password',
            'name' => fake()->name(),
            'marketing_opt_in' => false,
        ];
    }

    public function guest(): static
    {
        return $this->state(fn (array $attributes): array => [
            'password' => null,
        ]);
    }
}


Output:
Chunk ID: a246ca
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 199
Output:
<?php

namespace Database\Factories;

use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Customer>
 */
class CustomerFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'store_id' => Store::factory(),
            'email' => fake()->unique()->safeEmail(),
            'password' => 'password',
            'name' => fake()->name(),
            'marketing_opt_in' => false,
        ];
    }

    public function guest(): static
    {
        return $this->state(fn (array $attributes): array => [
            'password' => null,
        ]);
    }
}


Output:
Chunk ID: c87a1b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 228
Output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('customers', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('email');
            $table->string('password_hash')->nullable();
            $table->string('name')->nullable();
            $table->boolean('marketing_opt_in')->default(false);
            $table->timestamps();

            $table->unique(['store_id', 'email']);
            $table->index('store_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('customers');
    }
};