css-hidden-focus
Focusable elements must not be visually hidden
Checks that keyboard-focusable elements are not visually hidden by CSS techniques that can leave them in the tab order.
Applies to. Applies to elements that are tabbable (keyboard-focusable) but are visually hidden via CSS techniques that can leave them in the tab order.
Expectation. No element should be tabbable while visually hidden (e.g., opacity:0, clipped, off-screen). An element that CSS brings back into view when it takes focus is not hidden while focused, and is not flagged: the usual skip-link pattern (.skip { position: absolute; left: -9999px } .skip:focus { left: 0 }), or a hiding rule that stops applying on focus (.visually-hidden-focusable:not(:focus) { clip: rect(0 0 0 0) }). A focus rule nested in another (.skip { &:focus { top: 0 } }) counts as written out; one whose focus is inside :has() counts, for its subject, as :focus-within does. A focus rule that isn't !important doesn't override an !important declaration that hides the element.
What a finding reports.
metrics.visibilityHints: how the element is hidden while it can take focus:opacityZero,offscreen,clippedorzeroSizeOverflowHidden(no size, with overflow hidden) by its own styles; when they show it,outOfReach(a container is positioned off screen, or, in a browser, the box focus would show lies where no scrolling reaches) orcontainerClipped(a container is clipped to nothing, has no size with overflow hidden, or is scaled to nothing, or, in a browser, the element lies wholly outside a container withoverflow: clip, which focus doesn't scroll).runtimeProbe:nullunless focusing the element moved focus straight to another element (cssHiddenTabbable_runtimeRedirect_needsReview). Then it holdsredirected: trueand the tag and id of the element focus moved to (redirectedToTag,redirectedToId). Only the first three hidden elements are tried this way.
These data.details fields help to read and reproduce a finding. Apart from reasonCode and the few fields Output schema names as stable, they are not a stable contract: a field may be renamed or dropped in a minor release, so don’t build on them.
Examples
<a href="/x" style="opacity:0;">Hidden but tabbable link</a>
The link stays in the tab order despite opacity:0 making it invisible to sighted keyboard users.
<a href="/x">Visible link</a>
The link is both focusable and visible — nothing to flag.
<style>.skip { position: absolute; left: -9999px; } .skip:focus { left: 0; }</style><a class="skip" href="#main">Skip to content</a>
The link is off-screen at rest, but its :focus rule brings it back into view when it takes focus.
How to check
When this rule can’t decide, it reports cantTell and leaves the question to a person. Each question it can ask is listed here under its reason code, with the steps to answer it. A finding carries the same steps as howToCheck, in the scan’s language. See Output schema.
cssHiddenTabbable_needsFocusStateVerification
- Click just before the element the report names, then press Tab until focus should reach it.
- Check document.activeElement in the developer tools console to be sure focus is on it.
- Look for the element and its focus indicator on screen.
Pass. The element comes into view with a visible focus indicator while it has focus.
Fail. Focus lands on the element while it stays hidden, so a keyboard user cannot see where focus is, which fails WCAG 2.4.7.
cssHiddenTabbable_runtimeRedirect_needsReview
- Click just before the element the report names, then press Tab until focus should reach it.
- Watch where focus lands and check document.activeElement in the developer tools console.
- Press Shift+Tab from the control after it and see whether focus stops on the hidden element or passes straight on.
Pass. Focus never stays on the hidden element: it passes straight on to a visible control, as a focus trap's sentinel does.
Fail. Focus can stay on the hidden element, so a keyboard user cannot see where focus is, which fails WCAG 2.4.7.