Laravel Volt: Single-File Livewire Components Done Right
Laravel Volt lets you write Livewire components as single-file PHP closures — less boilerplate, faster iteration, and cleaner folder structures.
If you have worked with Vue or Svelte, you know the appeal of single-file components — one file holds the logic, the template, and sometimes the styles. PHP developers building reactive interfaces with Livewire have traditionally had to split that concern across two files: a PHP class in app/Livewire/ and a Blade view in resources/views/livewire/. Laravel Volt, released alongside Livewire 3 in 2023 and continuously refined through 2025, collapses that back into one. The result is a development experience that feels lighter, especially for smaller components that don’t need the ceremony of a full class.
What Volt Actually Is
Volt is not a separate package in the traditional sense — it ships as part of the Livewire 3 ecosystem. If you are on a fresh Laravel 11 or 12 install with Livewire 3, you already have it available. The concept is straightforward: instead of a PHP class, you write a single Blade file that uses Volt’s function-based API to declare state, computed properties, actions, and lifecycle hooks. Everything lives in one .blade.php file.
composer require livewire/livewire
php artisan livewire:volt counter
That generates resources/views/livewire/counter.blade.php — not a class file in app/Livewire/. Open it and you get a scaffold that looks roughly like this:
<?php
use function Livewire\Volt\{state, computed, action};
state(['count' => 0]);
$increment = action(fn() => $this->count++);
$decrement = action(fn() => $this->count--);
?>
<div>
<button wire:click="decrement">-</button>
<span>{{ $count }}</span>
<button wire:click="increment">+</button>
</div>
That is the entire component. No class, no separate view file, no render() method. Volt handles all the wiring under the hood — it compiles the file into a fully qualified Livewire component class at runtime.
The Function-Based API
Volt’s API maps cleanly to Livewire concepts. Here is a quick reference for the most common building blocks:
State — equivalent to public properties on a Livewire class:
state(['name' => '', 'email' => '', 'submitted' => false]);
Computed properties — cached, read-only derived values:
use function Livewire\Volt\{state, computed};
state(['items' => []]);
$totalPrice = computed(fn() => array_sum(array_column($this->items, 'price')));
In your template, access it as $this->totalPrice or use the @computed shorthand Blade integration.
Actions — methods called via wire:click, wire:submit, and so on:
use function Livewire\Volt\{state, action, rules};
state(['email' => '']);
rules(['email' => 'required|email']);
$subscribe = action(function () {
$this->validate();
Subscription::create(['email' => $this->email]);
$this->submitted = true;
});
Lifecycle hooks — mount, updated, hydrate, etc., all available as named functions:
use function Livewire\Volt\{state, mount};
state(['user' => null]);
mount(function (int $userId) {
$this->user = User::find($userId);
});
Lazy loading — tell Volt to defer rendering until after the initial page load:
use function Livewire\Volt\{state, lazy};
lazy();
state(['data' => []]);
Where Volt Shines
Volt is not a wholesale replacement for class-based Livewire components — it is a tool for a specific set of situations, and knowing which to reach for saves friction.
Simple, self-contained UI widgets are the sweet spot. A search box, a toggle, a modal trigger, a notification bell, a pagination control — these rarely need more than a handful of state variables and one or two actions. Writing a full class file for each one adds overhead without adding clarity. Volt makes these feel as lightweight as they should.
Rapid prototyping is another win. When you are sketching out a new feature, the friction of generating and naming a class file and then navigating between two files slows exploration. With Volt, the whole component is in front of you, and deleting it means deleting one file.
Co-location with routes pairs naturally with Laravel Folio, which provides file-based routing for Blade pages. A Folio page at resources/views/pages/dashboard.blade.php can @livewire a Volt component sitting right next to it in resources/views/livewire/dashboard/. Both features lean into the same “file location is the API” philosophy.
Volt and Folio Together
If you are using Laravel Folio for page routing, Volt integrates especially cleanly. Folio handles the URL-to-file mapping; Volt handles the reactive bits inside those pages. Here is a minimal setup:
composer require laravel/folio
php artisan folio:install
php artisan livewire:volt stats-widget
Inside your Folio page (resources/views/pages/dashboard.blade.php):
<x-layout>
<livewire:stats-widget :userId="auth()->id()" />
</x-layout>
The Volt component in resources/views/livewire/stats-widget.blade.php receives the prop via mount():
<?php
use function Livewire\Volt\{state, mount};
state(['userId' => null, 'stats' => []]);
mount(function (int $userId) {
$this->userId = $userId;
$this->stats = UserStats::for($userId)->toArray();
});
?>
<div>
<p>Total orders: {{ $stats['orders'] ?? 0 }}</p>
<p>Lifetime value: ${{ number_format($stats['ltv'] ?? 0, 2) }}</p>
</div>
The page routing, the component wiring, and the template are each in their natural place without any of them reaching into unfamiliar territory.
Limitations Worth Knowing
Volt components have a few restrictions compared to class-based Livewire. You cannot extend another Volt component — there is no inheritance model. Traits work, but you need to use them carefully since Volt compiles to an anonymous class internally. If your component is large enough that you want to extract helper methods or share behavior across multiple components via base classes, a proper Livewire class is the better fit.
Testing Volt components works exactly like testing any Livewire component. The generated class is accessible to Livewire::test():
use Livewire\Volt\Volt;
it('increments the count', function () {
Volt::test('counter')
->assertSee('0')
->call('increment')
->assertSee('1');
});
The Volt::test() helper resolves the component by its Blade path, which is more convenient than remembering the generated class name.
Performance Considerations
One concern developers raise is whether the compile step adds overhead in production. In practice, Volt caches the compiled class just like Blade caches compiled templates. On a warmed production server with php artisan view:cache in your deployment pipeline, there is no measurable difference between a Volt component and a hand-written class. The compilation only happens once per deployment, not per request.
Is It Worth Adopting?
For teams already on Livewire 3 and Laravel 11+, Volt is a low-risk addition. You do not need to migrate existing components — you can introduce Volt incrementally, using it for new small components while leaving existing class-based ones untouched. The two styles coexist without conflict.
The real argument for Volt is ergonomic. PHP developers sometimes look at JavaScript frameworks and feel like the frontend ecosystem has “nicer” component models. Volt is Laravel’s answer to that: the same reactive programming model, in PHP, in one file, with no build step beyond what you were already running.
If you have been deferring Livewire adoption because of the two-file overhead, Volt removes that barrier. And if you are already using Livewire, Volt is worth picking up for any component you would describe as “just a small piece of UI.”