4,000 emails/month for free | Mailtrap sends real emails now!

Everything new in Livewire 4

Published on by

Everything new in Livewire 4 image

Livewire 4 is finally here, and it's the biggest release yet.

This isn't about adding complexity—it's about better defaults, less friction, and more powerful tools for building exactly what you want. We've been heads-down for months rethinking how Livewire components should feel, and we're proud of where we landed.

Let's take a look.

View-based components

The most visible change in Livewire 4 is how you write components. Instead of bouncing between a PHP class and a Blade file, you can now put everything in a single file:

<?php // resources/views/components/⚡counter.blade.php
 
use Livewire\Component;
 
new class extends Component {
public $count = 0;
 
public function increment()
{
$this->count++;
}
};
?>
 
<div>
<h1>{{ $count }}</h1>
<button wire:click="increment">+</button>
</div>
 
<style>
/* Scoped CSS... */
</style>
 
<script>
/* Component JavaScript... */
</script>

This is now the default when you run php artisan make:livewire. The lightning bolt emoji makes Livewire components instantly recognizable in your file tree—you can immediately tell a Livewire component from a Blade component at a glance. (You can disable it if emojis aren't your thing.)

For larger components, there's also a multi-file format that keeps everything together in a single directory:

⚡counter/
├── counter.php
├── counter.blade.php
├── counter.css (optional)
├── counter.js (optional)
└── counter.test.php (optional)

Create one with --mfc, and convert between formats anytime with php artisan livewire:convert.

Routing

Reference components the same way everywhere. Livewire 4 introduces Route::livewire():

// Before (v3) - Still supported
Route::get('/posts/create', CreatePost::class);
 
// After (v4)
Route::livewire('/posts/create', 'pages::post.create');

The new syntax references components by name rather than class. This matches how you render components everywhere else in your app.

Namespaces

Livewire now ships with opinions about app structure. By default, you get two namespaces: pages:: for page components and layouts:: for layouts—everything else goes in resources/views/components alongside your Blade components.

Route::livewire('/dashboard', 'pages::dashboard');

For modular applications, you can register your own namespaces. Group admin components under admin::, billing under billing::, or whatever makes sense for your architecture.

Scripts and styles

Component JavaScript and CSS now live alongside your components. Add <script> and <style> tags directly in your template:

<div>
<h1 class="title">{{ $count }}</h1>
<button wire:click="$js.celebrate">+</button>
</div>
 
<style>
.title {
color: blue;
font-size: 2rem;
}
</style>
 
<script>
this.$js.celebrate = () => {
confetti()
}
</script>

Styles are automatically scoped to your component—your .title class won't leak to other parts of the page. Need global styles? Add the global attribute: <style global>.

Scripts have access to this for component context—an alias for $wire that you might be used to using.

Both are served to the browser as native .js/.css files that are automatically cached for optimal performance.

Islands

Islands are the headline feature of Livewire 4. They let you create isolated regions within a component that update independently:

<div>
@island
<div>
Revenue: {{ $this->revenue }}
<button wire:click="$refresh">Refresh</button>
</div>
@endisland
 
<div>
<!-- This won't re-render when the island updates -->
Other content...
</div>
</div>

When you click "Refresh," only the island re-renders. The rest stays untouched. Previously, you'd need to extract this into a separate child component with all the overhead of props and events to get a similar level of isolation.

The performance benefits go deeper than just DOM updates. When you combine islands with computed properties, only the data needed by that island gets fetched. If your component has three islands each referencing different computed properties, refreshing one island only runs that island's queries. You're isolating overhead from the database all the way to the rendered HTML.

Islands support lazy loading (lazy: true), naming for cross-component targeting (name: 'revenue'), and appending content for infinite scroll:

<button wire:click="loadMore" wire:island.append="feed">
Load more
</button>

Slots and attribute forwarding

If you've used slots and attribute forwarding in Blade components, you'll feel right at home.

Slots let parents inject content into children while keeping everything reactive:

<livewire:card :$post>
<h2>{{ $post->title }}</h2>
<button wire:click="delete({{ $post->id }})">Delete</button>
</livewire:card>

The slot content is evaluated in the parent's context, so wire:click="delete" calls the parent's method.

Attribute forwarding lets you pass HTML attributes through:

<livewire:post.show :$post class="mt-4" />
 
<!-- Inside post.show component -->
<div {{ $attributes }}>
...
</div>

Drag and drop

Drag-and-drop sorting, no external library required:

<ul wire:sort="reorder">
@foreach ($items as $item)
<li wire:key="{{ $item->id }}" wire:sort:item="{{ $item->id }}">
{{ $item->title }}
</li>
@endforeach
</ul>
public function reorder($item, $position)
{
// $item is the ID, $position is the new index
}

It handles smooth animations automatically. Add drag handles with wire:sort:handle, prevent interactive elements from triggering drags with wire:sort:ignore, and drag between multiple lists using wire:sort:group.

Smooth transitions

The wire:transition directive adds hardware-accelerated animations using the browser's View Transitions API:

@if ($showAlertMessage)
<div wire:transition>
<!-- Message smoothly fades in/out -->
</div>
@endif

For step wizards or carousels where direction matters, you can specify transition types:

#[Transition(type: 'forward')]
public function next() { $this->step++; }
 
#[Transition(type: 'backward')]
public function previous() { $this->step--; }

Then customize the CSS animations for each direction using ::view-transition-old() and ::view-transition-new() pseudo-elements.

Optimistic UI

Make your interfaces feel instant. These directives update the page immediately—no round-trip required.

wire:show toggles visibility using CSS (no DOM removal, no network request):

<div wire:show="showModal">
<!-- Hidden/shown instantly -->
</div>

wire:text updates text content immediately:

Likes: <span wire:text="likes"></span>

wire:bind binds any HTML attribute reactively:

<input wire:model="message" wire:bind:class="message.length > 240 && 'text-red-500'">

$dirty tracks unsaved changes:

<div wire:show="$dirty">You have unsaved changes</div>
<div wire:show="$dirty('title')">Title modified</div>

Loading states

In addition to the existing wire:loading from v3, Livewire 4 automatically adds a data-loading attribute to any element that triggers a network request.

This makes it simple to style loading states directly from CSS and target sibling, parent, or child elements:

<button wire:click="save" class="data-loading:opacity-50">
Save <svg class="not-in-data-loading:hidden">...</svg>
</button>

Inline placeholders

For lazy components and islands, the @placeholder directive lets you define loading states right next to the content they replace:

@placeholder
<div class="animate-pulse h-32 bg-gray-200 rounded"></div>
@endplaceholder
 
<div>
<!-- Actual content loads here -->
</div>

No separate placeholder views or methods—your skeleton lives within your component.

JavaScript power tools

When you need to drop into JavaScript, Livewire 4 meets you there.

wire:ref gives elements names you can target:

<livewire:modal wire:ref="modal" />
$this->dispatch('close')->to(ref: 'modal');

You can also access refs from component scripts:

<input wire:ref="search" type="text" />
 
<script>
this.$refs.search.addEventListener('keydown', (e) => {
// Handle keyboard events...
})
</script>

#[Json] methods return data directly to JavaScript:

#[Json]
public function search($query)
{
return Post::where('title', 'like', "%{$query}%")->get();
}
<script>
let results = await this.search('livewire')
console.log(results)
</script>

$js actions run client-side only:

<button wire:click="$js.bookmark">Bookmark</button>
 
<script>
this.$js.bookmark = () => {
this.bookmarked = !this.bookmarked
this.save()
}
</script>

Interceptors hook into requests at every level:

<script>
this.intercept('save', ({ onSuccess, onError }) => {
onSuccess(() => showToast('Saved!'))
onError(() => showToast('Failed to save', 'error'))
})
</script>

Use global interceptors for app-wide concerns like session expiration:

Livewire.interceptRequest(({ onError }) => {
onError(({ response, preventDefault }) => {
if (response.status === 419) {
preventDefault()
if (confirm('Session expired. Refresh?')) {
window.location.reload()
}
}
})
})

Upgrading

Livewire 4 maintains strong backwards compatibility. Your existing components will continue to work—the new single-file format is the default for new components, but class-based components remain fully supported.

Check out the upgrade guide →

If you want to see all of this in action, I recorded a new Laracasts series covering every feature in depth with real-world examples. Watch the Livewire 4 series →


Livewire 4 is available now:

composer require livewire/livewire:^4.0

For the complete documentation, visit livewire.laravel.com.

Caleb Porzio photo

Hi friends! I'm Caleb. I created Livewire and Alpine because I love building tools that make web development feel like magic. I also love to fly fish!

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Curotec

Curotec helps software teams hire the best Laravel developers in Latin America. Click for rates and to learn more about how it works.

Visit Curotec
Curotec logo

Curotec

World class Laravel experts with GenAI dev skills. LATAM-based, embedded engineers that ship fast, communicate clearly, and elevate your product. No bloat, no BS.

Curotec
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $3200/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
Cut PHP Code Review Time & Bugs into Half with CodeRabbit logo

Cut PHP Code Review Time & Bugs into Half with CodeRabbit

CodeRabbit is an AI-powered code review tool that specializes in PHP and Laravel, running PHPStan and offering automated PR analysis, security checks, and custom review features while remaining free for open-source projects.

Cut PHP Code Review Time & Bugs into Half with CodeRabbit
Get expert guidance in a few days with a Laravel code review logo

Get expert guidance in a few days with a Laravel code review

Expert code review! Get clear, practical feedback from two Laravel devs with 10+ years of experience helping teams build better apps.

Get expert guidance in a few days with a Laravel code review
PhpStorm logo

PhpStorm

The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

PhpStorm
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit

The latest

View all →
Statamic 6 Beta Now Available image

Statamic 6 Beta Now Available

Read article
Everything new in Livewire 4 image

Everything new in Livewire 4

Read article
Cache Without Overlapping in Laravel 12.47.0 image

Cache Without Overlapping in Laravel 12.47.0

Read article
Going Real-Time with Reverb - Laravel In Practice EP17 image

Going Real-Time with Reverb - Laravel In Practice EP17

Read article
PHP DebugBar v3 is released image

PHP DebugBar v3 is released

Read article
The Neuron AI Framework for PHP and Laravel image

The Neuron AI Framework for PHP and Laravel

Read article