Back to Blog
3 min read

Building a Multi-Step Quote Form in Laravel with Livewire: Structuring Step 5 for Specialized Services

Why Step 5 Needed Its Own World

We’re rebuilding AustinsElite — a full-service event catering platform — and one of our core challenges is the quote wizard. It’s not just a contact form; it’s how clients define complex service bundles, from basic drop-off to full staffing with specialty offerings like sushi catering.

Early in the rebuild, we tried cramming all service options into a single step. Big mistake. The UI got noisy, validation was a mess, and users felt overwhelmed. So we made a call: niche services needed their own dedicated step — Step 5.

This wasn’t just about UX. It was about separation of concerns. Sushi catering has unique requirements: raw fish handling, chef certifications, plating preferences, dietary restrictions, and prep time. These fields don’t belong next to a checkbox for ‘chafing dishes.’

So we carved out Step 5 as a conditional, service-specific gateway. If a user selects ‘Sushi’ or ‘Custom Dessert Stations’ in Step 3, Step 5 dynamically appears — tailored, focused, and loaded with only what matters.

Livewire as the State Glue (Without the JS Overhead)

Here’s where Livewire shines. Even though AustinsElite’s frontend feels dynamic and responsive, we’re not running a full SPA. We’re using Laravel 12 with Livewire to manage state, handle validation, and render partials — all server-side.

When the user selects specialty services in Step 3, Livewire tracks that in component state:

public $selectedServices = [];

public function updatedSelectedServices($services)
{
    $this->showStepFive = in_array('sushi', $services) || in_array('dessert-station', $services);
}

That single method controls both UI flow and backend logic. No API calls. No React context or Redux. Just PHP, reactivity, and zero frontend bloat.

In Step 5, we load a dedicated Livewire component — SushiQuoteForm — that handles its own validation rules, file uploads (like menu proofs), and dynamic sub-fields. Need to show extra options if the client wants live sushi rolling? Just toggle a property:

public $hasLiveRolling = false;

public function updatedHasLiveRolling()
{
    $this->validateOnly('hasLiveRolling');
}

Livewire re-renders only the relevant section. The user sees instant feedback, and we keep the payload tiny.

And because everything lives in the same request cycle, we don’t have to worry about syncing frontend and backend state. The form is the state.

Conditional Fields, Clean Validation, and Real-World Edge Cases

One of the trickiest parts of Step 5 was validation — especially when fields appear conditionally. Laravel’s validation rules are powerful, but you can’t just slap required on a field that’s sometimes hidden.

Our solution? Dynamic rule arrays built in real time:

protected function rules()
{
    return [
        'serving_count' => 'required|integer|min:10',
        'has_raw_fish' => 'boolean',
        'allergy_notes' => $this->hasRawFish ? 'required|string|max:500' : 'nullable',
        'event_location_type' => 'required|in:indoor,outdoor,vanue-approved',
        'onsite_power' => $this->eventLocationType === 'outdoor' ? 'required|boolean' : 'nullable',
    ];
}

This keeps validation tight and context-aware. No more false positives from hidden fields.

We also added real-time feedback using Livewire’s $dispatch to trigger toast notifications:

$this->validate();
$this->dispatch('quote-step-updated', step: 5);

On the frontend, Alpine.js listens for these events and scrolls the user smoothly to the next step — blending server-driven logic with lightweight interactivity.

The result? A form that feels fast, smart, and forgiving — even when asking about nori sheet thickness.

Building Step 5 taught us that complexity isn’t the enemy. Misplaced complexity is. By isolating specialized services and leaning into Livewire’s reactive PHP model, we kept the code clean, the UX focused, and the quote funnel converting. And honestly? It made building for niche catering a lot more fun.

Newer post

How We Fixed IDE Noise in Laravel with Intelephense Helper Files

Older post

Modernizing Legacy Laravel Frontends: How We Integrated UnDraw SVGs and Component Libraries in a Next.js Migration