Skip to content
LaravelBlog

livewire #10317: [4.x] Fix autofocus after `wire:navigate`

LaravelBlogBot
LaravelBlogBot

Contributed by joshhanley

Part of the livewire v4.4.7 release

Fix · Livewire v4.x · PR #10317 · fixes #10299

wire:navigate pages that rely on the autofocus attribute have long had a silent gap: the attribute worked on the first full page load, but was ignored on every client-side navigation after that. PR #10317 fixes this by finally enabling a helper that has been sitting dead in the navigate plugin's code, so any element marked autofocus now receives focus after a wire:navigate swap — including when you navigate back to a cached page. There is nothing to configure; the behavior simply starts working when you pick up this change.

What changed

Inside js/plugins/navigate/index.js, the navigate plugin already contained an autofocusElementsWithTheAutofocusAttribute() helper. It was wired up in both navigation paths — the normal swap path and the "restore from page cache" path (back/forward buttons) — but both call sites were gated behind a module-level let autofocus = false flag that was never flipped to true. The helper ran, but the condition made it a no-op.

This PR does three things:

  1. Removes the dead autofocus = false flag entirely.
  2. Calls autofocusElementsWithTheAutofocusAttribute() unconditionally in both navigation paths.
  3. Reorders the calls slightly: focus is now applied after Alpine is initialized on the new page (nowInitializeAlpineOnTheNewPage(Alpine) comes first), and the old setTimeout(...) wrapper is gone.

That last point matters for correctness. Running the autofocus helper after Alpine initialization ensures Alpine has finished setting up the new DOM before focus is moved, avoiding any race with initialization that could pull focus elsewhere.

Why it matters

Browsers only honor native autofocus during the initial document load. When wire:navigate swaps in a new page, the document has long since processed autofocus, so newly inserted elements with the attribute were simply skipped. The practical symptom: a search page or form whose input focuses itself on a hard load does nothing when you arrive there via wire:navigate.

This made autofocus effectively useless in any SPA-style Livewire app, forcing developers into manual workarounds — setTimeout(() => el.focus()) in an alpine:navigated listener, or similar. Notably, the contributor dug through the git history of both Livewire's and Alpine's navigate plugins looking for an intentional reason the flag was disabled, and found none. It appears to have been dead code from the start rather than a deliberate guard, which made enabling it safe.

Seeing it in practice

use Livewire\Component;

class FirstAutofocusPage extends Component
{
    public function render(): string
    {
        return <<<'HTML'
        <div>
            <input id="first-autofocus" autofocus type="text">

            <a href="/second-autofocus" wire:navigate>Go to next page</a>
        </div>
        HTML;
    }
}

class SecondAutofocusPage extends Component
{
    public function render(): string
    {
        return <<<'HTML'
        <div>
            <textarea id="second-autofocus" autofocus></textarea>
        </div>
        HTML;
    }
}

With this fix in place:

  • Visiting /first-autofocus focuses the first input (native behavior, unchanged).
  • Clicking the wire:navigate link swaps in the second page, and the textarea now receives focus.
  • Pressing the back button restores the cached first page — and the first input is focused again, because the cache-restore path now also applies autofocus.

If you need to override the behavior for a specific page, you still can: alpine:navigated fires after autofocus runs, so a listener can redirect focus wherever you like:

document.addEventListener('alpine:navigated', () => {
    document.querySelector('#my-input')?.focus()
})

Upgrading from v3.8.10

If you're coming from v3.8.10, the gap existed there too — the disabled flag goes back through the navigate plugin's history in both projects. So this arrives as a behavior change bundled with the v4.x line, and the impact is:

  • No API or config changes. There's no new directive, no flag to enable. Existing autofocus attributes just start working after navigation.
  • Audit pages that intentionally left focus alone. If any navigation flow silently depended on nothing being focused after a swap (e.g., to keep a keyboard shortcut handler or a global search trigger active), adding autofocus is now honored — simply remove the attribute from elements where you don't want focus moved.
  • Delete your workarounds. If you wrote custom JS to focus an element after wire:navigate (a common pattern to work around #10299), that code is now redundant and can be removed.
  • Accessibility is, if anything, improved. Moving focus to a newly rendered interactive element is generally what assistive technologies expect after a page-level content change.

The change is covered by two new browser tests in SupportNavigate/BrowserTest.php: one verifying forward navigation focuses the target page's autofocus element even when the previous page also had one, and one verifying focus is restored when navigating back to a cached page.

Takeaways

  • autofocus now works after wire:navigate, including on back/forward navigation to cached pages — fixing a long-standing gap (#10299).
  • The fix enables a previously dead helper and runs it after Alpine initialization, before alpine:navigated fires.
  • No configuration or template changes are required; remove autofocus where you don't want focus moved, or listen for alpine:navigated to override focus.
  • If you built a manual focus workaround after navigation, you can safely delete it.

Sources

More from this release

Related Articles