Features of a Livewire project: Livewire 4, volt, flux, Alpine...

Video thumbnail

Before starting to develop our application in Livewire, once the project in Laravel Livewire has been created, I want to take a short break to tell you what we have in a project with this framework.

In a nutshell, we can say that Livewire are “boosted” Laravel components. That is, with these components we can perform multiple operations that would normally require much more code or additional configurations.

When you start a project with Laravel Livewire, especially from version 12 onwards, you quickly discover that you are not working with “traditional Laravel,” but with something that—as happened to me the first time—feels like boosted Laravel components. In this article, I explain, from a practical perspective, what the real characteristics of a modern Livewire project are, how it is built, what it includes, what changes compared to previous versions, and how to get the most out of it.

We're going to talk about the most important aspects we have when creating a project in Laravel Livewire starting from version 12 of Laravel, which, as we mentioned before, unlike previous versions, no longer includes the functionalities we had in previous versions with Jetstream.

What does Livewire offer us?

Creating modern applications today is a challenge. Not only must we master the Backend (where Laravel is the undisputed king), but also the presentation layer or Frontend. Traditionally, this implied using frameworks like Vue or React, which forced us to separate our development into two distinct worlds, increasing complexity and maintenance.

This is where Livewire comes in: a full-stack framework for Laravel that gives us the benefits of reactivity (very similar to what Vue offers) while keeping us 100% integrated into the Laravel ecosystem. It is, literally, the best of both worlds.

Livewire is a complete framework for Laravel that simplifies the creation of dynamic interfaces without leaving the comfort of Laravel; in short, it allows us to use schemas similar to those of Vue and React directly in Laravel.

We can work with schemas similar to those of Laravel and Vue, but in a more direct and simple way; Laravel development is strongly linked to the use of components; the same Laravel components but with important additions from Laravel Livewire, which allows scaling the use of components enormously:

  1. Linking via the routes file.
  2. Simple communication based on actions, events, and functions between the client and the server.
  3. Adds features like pagination, file uploading, query string, among others.

In short, with Livewire, we can do the same things we do with Laravel and its controllers, but in a simpler way where we can use components and reuse pieces of code that Livewire offers us to make server-client communication very straightforward.

Reactivity without complications

For me, the best part of Livewire is the power of its events. In traditional development or with APIs, performing an action like "cloning a post" would require:

  • Creating a route.
  • Implementing a call with Axios.
  • Handling the response on the client side.

With Livewire, this is reduced to a wire:click (or similar) attribute that directly calls a server method. It's pure magic and simplifies the workflow in an incredible way.

Besides that, depending on how you decide to install Laravel Livewire, you can enable options that already come for free such as:

  1. Authentication system with registration and credential recovery.
  2. Profile view with user upload.
  3. Team management through teams.

In essence, Livewire is not a framework; you can see it as just another package that adds extra functionalities to some Laravel elements that turn it into a scaffolding or skeleton for our applications.

Use Cases: SEO and Dashboards

Livewire is extremely versatile. On my own blog, for example, I use a strategic combination:

  • Administration (Dashboard): I use Livewire to take advantage of development speed and reactivity in forms and tables.
  • Public part (Blog): I use Laravel directly because, as it's rendered on the server, the content is easily indexable by Google, unlike some heavy JS implementations or using Livewire.

A constantly evolving ecosystem

Livewire and Laravel change fast. Livewire releases major versions every so often, and Laravel does so annually. In this course, we will not only learn the theory but also modern implementation:

  • Single-file components: We will see how Livewire is migrating towards a structure similar to Vue's Single File Components, where logic and HTML can coexist to improve development speed.
  • Integration with Alpine.js: For those interactions that don't need to travel to the server, Livewire relies on Alpine.js, a lightweight JS framework that we will also cover.

What Livewire allows

With Livewire components we can:

  • Perform view routing: instead of a controller returning a view, we can do it using a component.
  • Use dynamic properties, similar to Vue's v-model, to reactively link data between the frontend and the backend.
  • Work with query strings to keep information in the URL and synchronize it with the component's state.
  • Handle file uploads easily.
  • And many other operations that we will see in practice.

I don't want to get too far ahead, because some functions are more technical, and it's better to explore them directly while building our application.

The important thing is to take away the idea: Livewire gives us improved Laravel components, with many ready-to-use functionalities, which allows us to develop dynamic applications without leaving the Laravel ecosystem.

Why choose Livewire for your Laravel project?

Livewire has positioned itself as a perfect intermediate solution between traditional Blade and SPA frameworks like Vue or React. It's ideal for those who want reactive interfaces without having to set up an entire modern JavaScript infrastructure.

In my first Livewire projects, one of the things that surprised me the most was being able to perform instant actions with wire:model, wire:click, or even manage Query Strings without writing a single line of JS. You literally connect a property to the view and the state flows.

Main advantages to highlight:

  • Elimination of complex JS for most interactions.
  • 100% native integration with Blade.
  • Reusable and nested components.
  • Lazy loading, instant validation, file uploads.
  • Ideal for panels, dashboards, CRUDs, and internal tools.

Essential elements of a Livewire project

Folder structure and components

A Livewire project in Laravel 12 is based on the idea that every part of your interface is a component. This includes forms, dashboards, sidebars, profiles, and any element that needs logic and a view.

In my first steps, I noticed that, unlike previous versions, the direct Livewire dependency no longer appears in composer.json: it now arrives through packages like Volt and Flux, which changes the project's architecture.

If you review the structure of a freshly installed Livewire project, you'll notice:

  • /app/Livewire → where the components live
  • /resources/views/livewire → Blade views of each component
  • /resources/views/components → auxiliary Blade components
  • /routes/web.php → routes that can point directly to Livewire components

It's common to see routes like:

Route::get('/dashboard', \App\Livewire\Dashboard::class);

Key dependencies: Volt, Flux, and more

In Livewire 12+, the ecosystem is reinforced with:

Volt

Volt allows building logic and view in a single file, something that feels almost like Svelte or Vue Single File Components, but directly in Blade.

A typical example:

<?php
use function Livewire\Volt\{state};
state(['count' => 0]);
$increment = fn () => $this->count++;
?>
<div>
  <h1>{{ $count }}</h1>
  <button wire:click="increment">+</button>
</div>

Volt is not one of my favorite technologies, as I don't like mixing logic with presentation, and in Livewire 4 we will even have a third way to create components in Livewire; even so, it is a completely valid structure.

Laravel Volt

In recent Laravel Livewire projects, starting from Laravel version 12, creating a project in Laravel Livewire includes other technologies such as:

Volt:

https://livewire.laravel.com/docs/volt

With which, we can create both the logic and the view (blade) in a single file:

<?php

use function Livewire\Volt\{state};

state(['count' => 0]);

$increment = fn () => $this->count++;

?>

<div>
   <h1>{{ $count }}</h1>
   <button wire:click="increment">+</button>
</div>

Laravel Flux UI

We will also see that the auxiliary components we use now are not the same as before; we now use Flux.

Flux, which is nothing more than a library of ready-to-use components for Laravel Livewire:

https://fluxui.dev/

<flux:input
   wire:model="email"
   :label="__('Email address')"
   type="email"
   required
   autocomplete="email"
   placeholder="email@example.com"
/>

Which you can expose to customize:

$ php artisan flux:publish

We also see a more complex structure, such as not having the Livewire dependency in composer.json, but rather the previous packages that import Livewire as a dependency.

Flux is a library of pre-built UI components that Livewire integrates to speed up development; it really has very good components. It is a freemium type, meaning it has free and paid components.

Components like:

<flux:input>
<flux:navlist>
<flux:dropdown>
<flux:menu>
<flux:profile>

For example:

<flux:input
  wire:model="email"
  label="Email"
  type="email"
  required
/>

These are Laravel components with tons of Tailwind classes, easy to extend:

$ php artisan flux:publish

With the command above, you can publish ALL flux components to analyze them.

Data-binding, props, and events (wire:model, actions)

Livewire allows views and logic to stay automatically synchronized.
Examples:

  • wire:model="email" keeps the state between input and component.
  • wire:click="save" executes backend methods without reloading.
  • wire:loading handles loading states.
  • wire:keyup.debounce.500ms optimizes events.

For me, the BEST thing about Livewire is wire:click.

What a Laravel Livewire Project Brings Us

In this section, we're going to talk about the most important aspects we have when creating a project in Laravel Livewire starting from Laravel version 13 with Livewire 4, which, as we mentioned before, unlike previous versions, it no longer includes the functionalities we had in previous versions with Jetstream, although they did include Team management again as an option.

The first thing we see is a couple of links for registration and login. At this point, if you haven't done it yet, create a user; once registered or logged in, you will have access to; create a user:

  • http://livewirestore.test/register

And it will send you to:

  • If you selected team: http://livewirestore.test/team-1/dashboard

  • Without team: http://livewirestore.test/dashboard

Publish the Livewire configuration file:

$ php artisan livewire:publish --config

Or:

$ php artisan livewire:config

Which is a configuration file where you will see the layout used:

config\livewire.php

'component_layout' => 'layouts::app',

I recommend that you review the mentioned file; you will see information indicating that it already injects the Livewire CSS and JS to be able to use the attributes and requests (which we will learn about later):

The view that will be used as the layout when rendering a single component as | an entire page via Route::livewire('/post/create', 'pages::create-post'). | In this case, the content of pages::create-post will render into .

You can create your layouts for other modules and override them in the configurations, although I recommend that you don't create them for now:

$ php artisan livewire:layout

And you can see that, at the path:

  • resources\views\layouts\app.blade.php

You will see the app layout:

<x-layouts::app.sidebar :title=" ?? null">
    <flux:main>
        {{  }}
    </flux:main>
</x-layouts::app.sidebar>

Which internally uses other components at the same level in an "app" folder:

  • app
    • header
    • sidebar

If, for example, you see the component:

resources\views\pages\settings\⚡profile.blade.php

You will see several interesting things, such as the single-file composition (HTML and PHP in the same file), anonymous classes, and the use of PHP attributes which are new since version 8:

new #[Title('Profile settings')] class extends Component {
    use ProfileValidationRules;

And the parameter passing:

resources\views\pages\settings\⚡profile.blade.php

Title('Profile settings')

resources\views\layouts\app.blade.php

<x-layouts::app.sidebar :title=" ?? null">

Previously, to pass parameters it was:

->layoutData(['title' => 'Teams'])

And best of all, NO route exists for this page:

Route::view('/', 'welcome', [
    'canRegister' => Features::enabled(Features::registration()),
])->name('home');
Route::prefix('{current_team}')
    ->middleware(['auth', 'verified', EnsureTeamMembership::class])
    ->group(function () {
        Route::view('dashboard', 'dashboard')->name('dashboard');
    });
Route::middleware(['auth'])->group(function () {
    Route::livewire('invitations/{invitation}/accept', 'pages::teams.accept-invitation')->name('invitations.accept');
});

And you can see from the dashboard, in the navbar, an option for the profile:

  • http://livewirestore.test/settings/profile

Why the ⚡ emoji?

You might wonder about the lightning bolt in the filename. This small detail has a practical function: it allows Livewire components to be instantly recognizable in the file tree and your editor's search results. Being a Unicode character, it works perfectly across all platforms: Windows, macOS, Linux, Git, and your production servers.

The emoji is entirely optional, and if you don't find it comfortable, you can disable it completely in your config/livewire.php file:

'make_command' => [
    'emoji' => false,
],

Single File Components (Livewire Volt)

Another novelty that might be striking is the component structure. Traditionally, we had a class in PHP and a view in Blade. Now, with Volt, everything can be united in the same block:

  1. Functional Logic: At the top, we have a PHP block (sometimes identified with an emoji in the editor) where we define properties and functions.
  2. Dynamic Attributes: We use PHP 8 attributes (like #[Layout]) to pass data directly to the layout, such as the page title.
  3. HTML: Right below, we have the Blade code.

It's a very powerful mix that's reminiscent of Vue components but processed entirely on the server. Since everything is in the same file, we no longer need a render() function, as Livewire assumes the HTML below is what should be displayed.

Blade, Blade Components, Layouts, and Flux

As you can see, x-layout-app corresponds to a component in base Laravel:

resources/views/components/layouts/app.blade.php

<x-layouts.app.sidebar :title=" ?? null">
   <flux:main>
       {{  }}
   </flux:main>
</x-layouts.app.sidebar>

Another thing about the previous code is that components starting with "flux" are the components mentioned at the beginning provided by the Livewire Flux package. There are many throughout the application for different purposes, but their names describe what they do quite well, for example:

<flux:navlist>
<flux:navlist.item>
<flux:heading>
<flux:subheading>
<flux:dropdown>
<flux:profile>
<flux:menu>
<flux:input>

In the end, all of these are just Laravel components with a lot of Tailwind classes, so you can inspect them from the browser; similarly, we will use these components little by little as we progress.

resources/views/components/layouts/app/sidebar.blade.php

<flux:sidebar sticky stashable class="border-r border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">

The rest of the actions created by the Laravel installer include things like changing the password and other user data, closing the account, dark mode, and a bit more. You can find these in the sidebar listed before at the bottom:

  • http://livewirestore.test/settings/profile

Routing, views, and single page (without traditional controller)

A Livewire component can replace a controller.
Classic example:

class Dashboard extends Component {
  public function render() {
     return view('livewire.dashboard');
  }
}

This simplifies the architecture by more than 40% in small/medium projects.

File upload, real-time validation, and lazy loading

Advanced Livewire features that are often highlights of the framework:

  • File upload with prior validation.
  • Instant validation with server rules.
  • Lazy loading for components that shouldn't load on startup.
  • Query Strings synchronizable with internal properties: useful for filters, tables, pagination.

When I implemented file uploads for the first time, I was surprised that I didn't have to write any JS for previews and states.

Analyzing a Livewire project

The route points directly to the component, without going through a traditional controller. The first time I did it, I thought, "wow, this simplifies tons of boilerplate."

But that's just the beginning. The most important thing is to understand how a modern Livewire project is structured, so let's get to that.

The first thing we see is a couple of links for registration and login; at this point, if you haven't already, create a user; once registered or logged in, you will have access to:

http://livewirestore.test/dashboard

Whose view corresponds to:

<x-layouts.app title="Dashboard">
   <div class="flex h-full w-full flex-1 flex-col gap-4 rounded-xl">
       <div class="grid auto-rows-min gap-4 md:grid-cols-3">
           <div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
               <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
           </div>
           <div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
               <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
           </div>
           <div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
               <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
           </div>
       </div>
       <div class="relative h-full flex-1 overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
           <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
       </div>
   </div>
</x-layouts.app>

As you can appreciate, x-layout-app corresponds to a base Laravel component:

resources/views/components/layouts/app.blade.php

<x-layouts.app.sidebar :title="$title ?? null">
   <flux:main>
       {{ $slot }}
   </flux:main>
</x-layouts.app.sidebar>

The strangest part of the previous code is the component that starts with flux, which are the components mentioned at the beginning provided by the Livewire Flux package. There are many of them throughout the application that serve different purposes, but their suffix already quite well exemplifies what they do, for example:

<flux:navlist>
<flux:navlist.item>
<flux:heading>
<flux:subheading>
<flux:dropdown>
<flux:profile>
<flux:menu>
<flux:input>

In the end, all of these are nothing more than Laravel components with a lot of Tailwind classes, so you can inspect them from the browser; similarly, we will be using these components little by little as we progress.

resources/views/components/layouts/app/sidebar.blade.php

<flux:sidebar sticky stashable class="border-r border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">

The rest of the available actions created by the Laravel installer include actions like changing the password and other user data, closing the account, dark mode, and little else; you have this listed in the sidebar below:

http://livewirestore.test/settings/profile

Best practices you should follow

Performance and lazy loading

  • Use lazy when possible (wire:model.lazy).
  • Divide large components into subcomponents.
  • Avoid unnecessary renders using ->skipRender().
  • Load scripts only when needed.

SEO, accessibility, and user experience

Although Livewire is not a complete SPA framework, it does support:

  • dynamic metas
  • page titles
  • fast navigation
  • accessible components (especially with Flux UI)

You can really use aspects of Livewire along with classic Laravel, and this website is an example of that: I use Laravel Livewire for the Dashboard, and base Laravel for the blog.

Testing, maintenance, and scalability

  • Use tests with Livewire::test().
  • Maintain consistent naming between components and views.
  • Version UI components.
  • Avoid mixing too much logic into a single component (maximum 1 responsibility).

In summary: working with Livewire today feels modern, flexible, and much simpler than before, although unfortunately, pre-defined options previously available with Jetstream, such as avatar upload, session, roles/teams, are no longer included.

FAQs: Frequently Asked Questions about Livewire projects

  • Does Livewire replace Vue or React?
    • It depends on the type of project: for internal panels and CRUDs, yes; for ultra-interactive apps, no.
  • Can I use it along with Alpine.js?
    • Yes, it's a very common combination.
  • Is it suitable for large projects?
    • Yes, especially with well-divided components. The community on Reddit shares good experiences in medium/large projects.
  • What changes in Laravel 12?
    • Volt and Flux become part of the modern Livewire ecosystem, and Jetstream no longer includes as many predefined features.

Now, let's get to know the key piece of Livewire, the components.

We are going to present what we have in detail, the project, its characteristics and how it works.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español
<script> window.addEventListener('scroll', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); window.addEventListener('mousemove', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); window.addEventListener('touchstart', function() { if (window.scriptsLoaded) return; loadThirdPartyScripts(); }, { once: true }); // Fallback if no interaction window.addEventListener('load', function() { setTimeout(function() { if (!window.scriptsLoaded) loadThirdPartyScripts(); }, 8000); }); function loadThirdPartyScripts() { if (window.scriptsLoaded) return; window.scriptsLoaded = true; console.log('Loading third party scripts...'); // Google Analytics var gtagScript = document.createElement('script'); gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-F22688T9RL'; gtagScript.async = true; document.head.appendChild(gtagScript); gtagScript.onload = function() { window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-F22688T9RL'); }; // Google ADS const adScript = document.createElement('script'); adScript.src = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"; adScript.setAttribute('data-ad-client', 'ca-pub-5280469223132298'); adScript.async = true; document.head.appendChild(adScript); // Facebook Pixel (function(f, b, e, v, n, t, s) { if (f.fbq) return; n = f.fbq = function() { n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments) }; if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = '2.0'; n.queue = []; t = b.createElement(e); t.async = !0; t.src = v; s = b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t, s); })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '1643487712945352'); fbq('track', 'PageView'); } </script> <noscript> <img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1643487712945352&ev=PageView&noscript=1" /> </noscript>