Skip to content

fix(anchor-menu): keep sticky menu keyboard-reachable at 200% zoom#1713

Open
juanmitriatti wants to merge 1 commit into
devfrom
UDS-2160
Open

fix(anchor-menu): keep sticky menu keyboard-reachable at 200% zoom#1713
juanmitriatti wants to merge 1 commit into
devfrom
UDS-2160

Conversation

@juanmitriatti

@juanmitriatti juanmitriatti commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Description

Problem. Across all pages with the anchor menu, zooming to 200% on desktop
made the menu unreachable by keyboard: tabbing through the page skipped the
anchor menu entirely, and the only way to reach it was to zoom back below 200%.
(Reported in the accessibility audit — UDS-1638 / UDS-2160.)

Root cause. The sticky behavior made the menu "stick" by physically
relocating the node in the DOM
globalHeader.appendChild(navbar) in
anchor-menu.js. Because tab order follows DOM order, attaching the menu
moved its focusable controls from their in-content position to inside the
global header
— i.e. earlier in the document than the user's current focus.

At 200% zoom the effective width drops below the lg breakpoint, so the menu is
a collapsed sticky bar. As a keyboard user tabs down the page, the scrolling
that tabbing causes triggers the relocation, and the menu jumps to a point the
user has already passed. Forward Tab then never lands on it.

Fix. Pin the menu in place with CSS instead of moving the node:
position: fixed; top: var(--uds-anchor-menu-top), where the JS sets
--uds-anchor-menu-top to the global header's bottom offset. The node never
leaves its document position, so the tab order is preserved. Attach/detach is
now driven by a scroll threshold against the stored original offset (rather than
reading the live rect of an element that is position: fixed). This mirrors the
already-accessible unity-react-core AnchorMenu, which has always positioned
in place via the same CSS variable — so this also removes divergence between the
two implementations.

Fixes WCAG 2.1.1 Keyboard (Level A) and WCAG 2.4.3 Focus Order (Level A).

Why not just add a tabindex?

The element was never missing from the tab order, so tabindex does not address
this bug:

  • The toggle is already a native <button> with tabindex = 0 and is fully
    focusable (verified). The problem was never focusability.
  • tabindex="-1" makes an element focusable only programmatically and
    removes it from the sequential tab order — the opposite of what we need.
  • tabindex="0" is redundant here — the button is already in the tab order.
  • A positive tabindex (1+) is a WCAG anti-pattern and still wouldn't help,
    because the node is physically relocated regardless of its tabindex.

The defect is a DOM-order problem, not a focusability one: the menu node is
moved to before the user's current focus position while they tab, and no
tabindex value changes where a node sits in document order
. The only real
options are (a) don't move the node — position it in place with CSS (this PR),
or (b) keep the relocation and add focus-management hacks to compensate (fragile,
and it treats the symptom). This PR takes (a), fixing the root cause.

Testing Steps

  1. Open the anchor menu story (Atoms → Anchor Menu).
  2. On a wide desktop window, set browser zoom to 200% (the menu collapses to
    the "On This Page:" bar).
  3. Scroll down until the menu sticks below the header.
  4. Tab through the page from the top with the keyboard.
  5. Expected: focus reaches the "On This Page:" toggle (and its links once
    expanded). Before this change, Tab skipped the menu entirely.
  6. Regression check: at normal zoom / desktop width, the expanded menu still
    sticks, the links are reachable, click-to-scroll and active-link highlighting
    still work, and scrolling back to the top detaches the menu cleanly.

Equivalent to step 2, you can instead narrow the window to < ~640px to simulate
200% zoom.

Checklist

  • Tests pass for relevant code changes

Links

The sticky behavior relocated the menu in the DOM (globalHeader.appendChild),
which moved its focusable controls out of the keyboard tab order. At 200%
desktop zoom (effective width < lg) the menu is a collapsed sticky bar, so
forward Tab skipped it entirely and keyboard users could not reach it.

Pin the menu in place with CSS (position: fixed; top: var(--uds-anchor-menu-top))
instead of moving the node, mirroring the unity-react-core AnchorMenu. The node
never leaves its document position, so tab order is preserved. Attach/detach is
now driven by a scroll threshold against the stored original offset rather than
the live rect of a now-fixed element.

Fixes WCAG 2.1.1 Keyboard (Level A) and 2.4.3 Focus Order (Level A).
UDS-1638 / UDS-2160
@juanmitriatti juanmitriatti requested a review from a team as a code owner June 9, 2026 11:03
@juanmitriatti juanmitriatti self-assigned this Jun 9, 2026
@asu-jenkins-devops

Copy link
Copy Markdown
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants