a11y(2.4.3): route navigation — move focus to the main landmark on afterNavigate, not just scroll#3538
Merged
Merged
Conversation
After every route navigation focus was falling to document.body, forcing keyboard users to re-navigate from the document top. Moving focus to <main id="content"> gives SR users a route-change announcement and lands keyboard users just before the page's first interactive element. preventScroll: true avoids re-scrolling after the preceding scrollTo(0, 0). Depends on <main tabindex="-1"> which is already present in main-content-container.svelte. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The unconditional main.focus() fired on every navigation, including same-pathname query-param navs done via updateQueryParameters (keepFocus: true). Filtering, paginating, sorting, and searching list views would yank focus out to <main> mid-interaction, defeating keepFocus and regressing the very keyboard/SR users 2.4.3 targets. Guard on the navigation target: skip initial hydration (type 'enter') and same-pathname query updates; only move focus on an actual route (pathname) change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pe 'enter'
Because the app runs SPA (ssr=false) and splits (login)/(app) route
groups, the (app) layout frequently mounts AFTER the post-login/root
client redirect. Its first afterNavigate then fires with type 'goto'
(from /login or /), never 'enter', so the type==='enter' guard missed
it and focus was stolen on what users see as first load (visible ring
around <main>).
Track the layout's first navigation with a flag and skip it instead —
robust to both direct hydration ('enter') and post-redirect entry
('goto'). Same-pathname query-param navs are still skipped to respect
keepFocus.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The initialNavigation flag was insufficient. OSS ui's root route (/) renders a Loading page and redirects to workflows via goto() in onMount — a second, programmatic navigation. So afterNavigate fires twice on first load: 'enter' on / (consumes the flag), then 'goto' from / to /workflows (still focuses). The flag just deferred the focus-steal by one navigation. The / route exists only to load and redirect away, so any navigation from / is the bootstrap hop, not a user action. Guard on that instead. type==='enter' covers direct deep links; same-pathname covers query navs. (cloud-ui needs none of this — its root redirects in load, so it lands as 'enter'.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ardiewen
approved these changes
Jun 25, 2026
Contributor
|
Pushed two follow-up commits to harden the
Final guard: skip Note: the description's "afterNavigate does not fire on first load" is inaccurate — it fires as |
temporal-cicd Bot
pushed a commit
that referenced
this pull request
Jul 2, 2026
Auto-generated version bump from 2.51.1 to 2.52.0 Bump type: minor Changes included: - [`9c6fcd54`](9c6fcd5) Add direct SSO redirect and backend logout (#3590) - [`feab0d90`](feab0d9) a11y(4.1.3): Toaster role="log" → per-toast role="status"/"alert" for correct AT announcement semantics (#3560) - [`d7bbfb01`](d7bbfb0) a11y(2.5.8): toast — replace bare close button with IconButton (36×36 px) (#3553) - [`4efd6ab6`](4efd6ab) a11y(3.3.1): Holocene input primitives — add aria-invalid, aria-describedby, and uniform live-region error announcement (#3554) - [`0600b700`](0600b70) Use green only (#3599) - [`94107549`](9410754) Clarify activity pause timeout behavior (#3600) - [`9f88d5a8`](9f88d5a) Bump node version to 22.23.1 or newer to fix issue from previous security release (#3604) - [`ca4104ae`](ca4104a) Update reason copy and inputs (#3588) - [`51db73fa`](51db73f) Update batch operation reason input hint (#3605) - [`836d7c55`](836d7c5) a11y(2.4.3): route navigation — move focus to the main landmark on afterNavigate, not just scroll (#3538) - [`4ee95696`](4ee9569) Improve re-renders for large encoded event histories (#3592) - [`fa5e7aa1`](fa5e7aa) feature/schedules (#3603) - [`0d9467fd`](0d9467f) Default includeHeartbeatDetails and includeLastFailure to true for getActivityExecution and pollActivityExecution (#3521) - [`e5bc514e`](e5bc514) fix schedules: duplicate key (#3612) - [`f1eb489f`](f1eb489) Remove preview badge (#3581) - [`1e5173b4`](1e5173b) Add existing versions to worker create deployment version form (#3601) - [`a8552bae`](a8552ba) fix schedules -e.trim not a function (#3613) - [`0442f847`](0442f84) Fix width of ComputeBadge in VersionTableRow (#3615) - [`ddbeb54f`](ddbeb54) fix(cloud-nav): animate and stabilize side-nav collapse (#3606) - [`3cfc684b`](3cfc684) fix - schedules recent/upcoming runs - sort issue (#3616) - [`92b8875b`](92b8875) Move timestamp out of translate (#3617) - [`901258af`](901258a) Fix SAA and WF consistency (#3614) - [`99593d97`](99593d9) DT-4001 - standalone nexus operations (#3496) - [`f10d1bf1`](f10d1bf) Clamp codeblock to container width (#3621) - [`0a9d93ce`](0a9d93c) Remove unnecessary bottom-0 (#3623) - [`9dabb522`](9dabb52) chore(deps-dev): bump vite from 6.4.2 to 6.4.3 (#3610) - [`5c1854d3`](5c1854d) News Feed (#3596)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes WCAG 2.2 SC 2.4.3 (Focus Order) for route-change navigation in
src/routes/(app)/+layout.svelte.The
afterNavigatecallback scrolled#contentto the top but never moved keyboard focus. After every in-app navigation, focus fell todocument.body— forcing keyboard users to Tab from the document beginning and depriving screen-reader users of any route-change announcement.Fix: resolve
#contentonce, reuse for both the existingscrollToand a newfocus({ preventScroll: true })call.afterNavigate(() => { - document.getElementById('content')?.scrollTo(0, 0); + const main = document.getElementById('content'); + main?.scrollTo(0, 0); + main?.focus({ preventScroll: true }); });preventScroll: trueprevents the browser re-scrolling the element into view after our ownscrollTo(0, 0)has already positioned it.<main id="content" tabindex="-1">is already present insrc/lib/holocene/main-content-container.svelte(the dependency from2.4.1-skip-link-target-focus.mdis met).Parallel fix: the identical change is applied to
cloud-ui-mainin a companion PR (the+layout.svelteroute shell does not cascade via the@temporalio/uitarball).Screenshots
No visual change. Focus position changes after route transitions.
Design
N/A.
Testing
<main>, not at the skip-nav link.Cmd+K→ confirm Tab lands inside<main>on the loaded page.<main>.#contentis scrolled to top on each route change (existingscrollTopreserved).afterNavigatedoes not fire on first load).Checklist
Docs
No documentation changes required.
A11y-Audit-Ref: 2.4.3-route-change-focus