Back to Blog
3 min read

From Spaghetti to Structure: Modernizing Laravel Views with x:: Blade Components in a Legacy Codebase

The Tangled Web of Legacy Blade Templates

Fifteen years ago, AustinsElite was built on a custom PHP framework with just enough Laravel packages to feel familiar — but none of the modern tooling we take for granted today. Over the years, the Blade templates turned into a patchwork of duplicated logic, inconsistent markup, and copy-pasted conditionals. A simple status badge could appear in five different forms across the app, each with slightly different classes, logic, and inline styles.

We had buttons styled like links, links acting like buttons, and tables with hardcoded filters that couldn’t be reused anywhere else. Maintenance was a game of whack-a-bug: fix one inconsistency, and two more popped up elsewhere. With no real component system, even junior devs were afraid to touch the UI.

Something had to change — but rewriting the frontend wasn’t an option. We needed a way to bring structure without starting over.

Introducing x:: Blade Components — Incremental Sanity

Laravel’s x-component syntax (<x-button>, <x-badge>, etc.) has been around for a while, but in a legacy app like AustinsElite, it’s not just convenient — it’s transformative. We started small: identifying recurring UI patterns and wrapping them in reusable Blade components using the x:: convention.

Take status badges. We replaced this:

<!-- Before: scattered, inconsistent -->
@if($user->active)
    <span class="px-2 py-1 bg-green-500 text-white rounded text-xs">Active</span>
@else
    <span class="px-2 py-1 bg-gray-400 text-white rounded text-xs">Inactive</span>
@endif

With this:

<!-- After: standardized, reusable -->
<x-status-badge :active="$user->active" />

The component itself lives in resources/views/components/status-badge.blade.php:

@props(['active' => false])
<span 
    class="px-2 py-1 rounded text-xs text-white {{ $active ? 'bg-green-500' : 'bg-gray-400' }}"
>
    {{ $active ? 'Active' : 'Inactive' }}
</span>

We did the same for tables, filters, modals, and bulk action toolbars. One of the most impactful additions was <x-bulk-actions>, which abstracted the logic for selecting rows, confirming mass deletes, and triggering backend jobs — all wrapped in a clean, composable interface.

Even better? These components work alongside old templates. We didn’t need to refactor everything at once. We just started using x:: components in new features and gradually replaced hotspots during maintenance.

Measurable Gains: Cleaner Code, Faster Teams

Six months after introducing x:: components, the difference is measurable. Our Blade template duplication dropped by an estimated 60% — no more hunting down every instance of a button style. New developers can now understand UI patterns in hours, not days, because components document themselves.

But the real win is velocity. Before, adding a new filter panel meant copying and tweaking an existing one, risking subtle bugs. Now, we drop in <x-filters.container> and <x-filters.text> with consistent behavior and styling. What used to take 30 minutes now takes 5.

We’ve also seen fewer CSS regressions. With styles centralized in components (and scoped via Tailwind), we’re not fighting specificity wars anymore. Even our design system benefits — designers know what’s possible because the component library is the source of truth.

This wasn’t a big-bang rewrite. It was a quiet, consistent push toward maintainability. By leaning into Laravel’s modern Blade features, we gave a legacy app a second life — without throwing away 15 years of business logic.

If you’re stuck in a legacy PHP app with messy views, don’t reach for a framework migration or a full frontend overhaul. Try <x::>. Sometimes, the smallest tags make the biggest difference.

Newer post

How We Decoupled Business Logic in a Legacy PHP App Using a New Service Layer

Older post

From Prototype to Production: Extracting LiveBind into a Standalone TypeScript Library