livewire/livewire: Add renderless wire model
Contributed by sohailahmad07
Part of the livewire v4.4.7 release
Pull request #10363 extends Livewire's renderless pattern from actions to model bindings. If you have already used wire:click.renderless, the idea is familiar: the server handles the request, but the component is not re-rendered afterward. This change brings the same capability to wire:model, so a live model update can be persisted or processed on the server without morphing the current DOM. For anyone upgrading from v3.8.10, this is a purely additive feature — no configuration or template migrations are required — but it unlocks a cleaner pattern for autosave-style inputs and other server-side work that should stay invisible to the rendered page.
The new modifier
The headline syntax is a new modifier on wire:model:
<input type="text" wire:model.renderless.live="title">
Typing in this field still sends the updated value to the server: the property is set, and any server-side logic tied to that update runs. What does not happen is the render. The response contains no fresh HTML, so Livewire never morphs the DOM when the request completes.
The documentation gains a new "Skipping renders" section under wire:model, plus a .renderless row in the modifier table ("Skip re-rendering after a live model update"). Note the guidance there: because .renderless only affects server messages, pair it with .live as shown above.
How it works
The implementation has two halves. On the JavaScript side, wire:model compiles down to an Alpine x-model binding. Since renderless is a Livewire concept — not an Alpine modifier — the directive now strips it before building that binding:
modifiers = modifiers.filter(m => m !== 'renderless')
The original modifier list is preserved on directive.modifiers, though, so the request layer can still detect .renderless and flag the outgoing model request as renderless.
On the backend, live model updates arrive as $commit magic actions. HandleComponents::callMethods() now inspects the call metadata and skips the render when the flag is present:
// Support `.renderless` on magic actions like `wire:model.renderless.live`...
if ($metadata['renderless'] ?? false) {
$root->skipRender();
}
The PR scopes this handling to live model commits — a model.live type on a $commit call — so unrelated magic actions cannot accidentally inherit renderless behavior.
A new browser test makes the semantics concrete: after typing into the field, the front-end state ($wire.title) shows the new value while the server-rendered output stays untouched and the render counter never moves. Only after clicking a button wired with wire:click="$commit" does the rendered output catch up. That $commit call doubles as an escape hatch for when you deliberately want a render on demand.
Why it matters
Skipping the render and morph cycle is exactly what you want when the server needs the data but the DOM should be left alone. The PR description highlights two use cases: model updates that must be persisted or processed server-side, and validation state that should be updated on the server and read from JavaScript through $errors, all without re-morphing what is on screen.
A practical autosave sketch:
<input type="text" wire:model.renderless.live="title">
public function updatedTitle($value)
{
Draft::updateOrCreate(
['user_id' => auth()->id()],
['title' => $value],
);
}
Because only the render step is skipped, your regular update hooks still fire server-side, so this pairs naturally with existing data-binding hooks instead of requiring a dedicated action per keystroke. It also sidesteps the classic annoyance of wire:model.live morphs interfering with fast typing or third-party widget state.
Upgrading from v3.8.10
There is nothing to migrate: the change is additive, and existing wire:click.renderless usage behaves exactly as before. One subtle upgrade note: on v3.8.10, .renderless on a model directive was simply an unrecognized modifier (Alpine ignores unknown x-model modifiers), so it did nothing. After upgrading, that markup starts working — it is worth grepping your templates for any speculative wire:model.renderless usage written in anticipation, since it will now take effect. Two new browser regression tests — one for wire:model.renderless.live and one for a renderless magic action like wire:click.renderless="$set('title', 'hello')" — lock the behavior in.
Takeaways
wire:model.renderless.live="..."sends the model update to the server but skips the render and the resulting DOM morph..renderlessis filtered out before the Alpine binding is built, keeping it Livewire-scoped; the request layer flags the commit, and the backend callsskipRender()for live model commits only.- Only rendering is skipped: server state and lifecycle hooks still run, and
wire:click="$commit"forces a normal render whenever you want one. - Upgrading from v3.8.10 requires no changes, but check for pre-existing
wire:model.renderlessmarkup — it will now actually do something.
Categories: 2 (Feature), 6 (Documentation)