Keyboard focus is left unpredictable after push_patch in Phoenix LiveView
0 reputation · 20 May 2023, 04:25 UTC
0 reputation · 20 May 2023, 04:25 UTC
Phoenix LiveView patches the DOM over a WebSocket connection instead of reloading the page, and patched content is not announced to assistive technology on its own. Documented guidance puts that burden on authors: mark dynamic regions with aria-live, as the generated core_components.ex flash component already does, and reach for phx-hook when declarative bindings fall short.
The unsettled piece is focus. After a push_patch or push_navigate, LiveView does not restore focus to a predictable element, so keyboard and screen-reader users can lose their place in the page. Community discussion treats this as an open design question rather than a documented default, and generated markup differs across Phoenix and LiveView versions, so any conclusion should be verified against the installed release — assumed here to be a Phoenix 1.7-era app.
The decision is framework-wide. A documented default would shape every generated app at once, while a per-app policy leaves focus work to each team via phx-hook.
push_patch, the new view's main heading after push_navigate — or is explicit per-app handling the intended long-term answer?push_patch and push_navigate today, and is that difference documented for the current release?32275 reputation · 20 May 2023, 10:45 UTC
push_patch or push_navigate. Because LiveView patches the DOM via WebSockets rather than performing a full page reload, the element that held focus is often removed or replaced, causing the browser to reset focus to the or lose it entirely.The behavior stems from how LiveView manages the DOM diffing. When push_patch is called, LiveView calculates the difference between the current and new state and updates only the necessary nodes. If the element that triggered the patch is part of the content being replaced, the browser loses the focus context. Unlike a traditional page navigation where the browser naturally starts at the top of the document, LiveView's partial updates leave the focus in an indeterminate state for keyboard and screen-reader users.
In current releases (Phoenix 1.7+), the behavior is largely consistent between both methods regarding native focus:
Since the framework does not provide a global declarative focus target, the intended long-term solution is explicit per-app handling using phx-hook. This allows you to intercept the lifecycle events and manually move focus to a logical element.
Example of a hook to restore focus to a main heading after a patch:
let hook = {
mounted() {
this.focus;
// Wait for the next tick to ensure the patch is applied
const header = this.el.querySelector('h1');
if (header) {
header.focus();
}
}
}
To use this, register the hook in your hook.js and apply it to your main container: <div phx-hook="FocusHeader">...</div>.
Are you attempting to return focus to the specific element that triggered the action, or to the top of the new content? (This diagnostic detail determines the specific hook logic required.)
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.