Focused element partially obscured
focus/obscured-enhanced
- WCAG success criterion
- 2.4.12 (level AAA)
- Outcome class
- Hint only. An automated check that by design reports review hints only: its findings are marked for manual review, never as confirmed violations.
- Area
- Accessibility, Navigation & Operation
- Default severity
- medium
- axe-core rule
focus-not-obscured-enhanced
Why we flag this
Keyboard users tab to an element whose visible area is partially covered by a sticky header, footer, cookie banner, or similar overlay.
Focus pass as for focus/obscured; at least one sample point lies under a position: fixed/sticky element.
What the rule checks
Scope. Pages with rendered keyboard-focusable controls and at least one visible fixed or sticky element.
Expectation. needs_review once per control of which at least one sampled point (3 x 3 grid, inset 1 px, after focusing and scrolling into view) lies under a fixed or sticky element painted above it. An entirely covered control is reported here as well as under 2.4.11. A 1 px edge contact does not reach a sampled point. Not reported: an overlay that is visibility: hidden, an overlay painted below the control (z-index), a control inside the overlay. An overlay fixed only on the phone viewport, or added by script after load, counts. A page whose scripts break the probe records the rule as not run (CapabilityMissing).
Source. WCAG 2.2 SC 2.4.12 Focus Not Obscured (Enhanced) (AAA)
Detection: shared; needs dom, geometry, computed_style, interaction.
Failing example
The check flags this for review. Corpus case interaction_focus_obscured, element a#fo-covered. The page may contain further findings for other rules.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Focus hidden behind fixed content</title>
<style>
body { font-family: sans-serif; margin: 0; padding: 480px 16px 16px; color: #111; background: #fff; }
a, button { display: inline-block; min-width: 44px; min-height: 44px; padding: 12px; box-sizing: border-box; margin: 0; }
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 16px; top: 520px; }
.probe { position: absolute; width: 140px; height: 44px; background: #fff; }
#cookie { position: fixed; top: 0; left: 0; right: 0; height: 120px; background: #222; color: #fff; padding: 12px; box-sizing: border-box; }
#cookie button { background: #fff; color: #111; }
#hidden-bar { position: fixed; top: 150px; left: 0; right: 0; height: 60px; background: #222; visibility: hidden; }
#backdrop { position: fixed; top: 220px; left: 0; right: 0; height: 60px; background: #e8eef8; z-index: -1; }
#mobile-bar { display: none; }
@media (max-width: 600px) {
#mobile-bar { display: block; position: fixed; top: 300px; left: 0; right: 0; height: 60px; background: #222; }
}
</style>
</head>
<body>
<a class="skip" href="#main">Skip to main content</a>
<header><p style="margin: 0">Example Insurance</p></header>
<nav aria-label="Main">
<a href="/" aria-current="page">Home</a>
<a href="/claims">Claims</a>
</nav>
<main id="main">
<!-- Controls placed at fixed spots of the first screen, so each meets exactly one overlay. -->
<!-- needs_review (2.4.11 and 2.4.12): entirely under the cookie banner -->
<a id="fo-covered" class="probe" href="/privacy" style="top: 40px; left: 24px;">Privacy</a>
<!-- needs_review (2.4.12 only): 20 of 44 px under the banner -->
<a id="fo-partial" class="probe" href="/imprint" style="top: 100px; left: 24px;">Imprint</a>
<!-- absent: 1 px edge contact, within the rounding tolerance -->
<a id="fo-edge" class="probe" href="/contact" style="top: 119px; left: 180px;">Contact</a>
<!-- absent: the bar above it is visibility: hidden -->
<a id="fo-under-hidden" class="probe" href="/jobs" style="top: 160px; left: 24px;">Jobs</a>
<!-- absent: the fixed backdrop paints below the link (z-index: -1) -->
<a id="fo-above-backdrop" class="probe" href="/press" style="top: 230px; left: 24px;">Press</a>
<!-- needs_review (2.4.11 and 2.4.12) on the mobile viewport only -->
<a id="fo-mobile" class="probe" href="/app" style="top: 310px; left: 24px;">Get the app</a>
<!-- needs_review (2.4.11 and 2.4.12): covered by a chat widget a script adds after load -->
<a id="fo-dynamic" class="probe" href="/support" style="top: 410px; left: 24px;">Support</a>
<h1>Make a claim</h1>
<p>Report damage online or <a href="/call">call us</a>.</p>
</main>
<footer><p>Example Insurance footer</p></footer>
<div id="hidden-bar"></div>
<div id="backdrop"></div>
<div id="mobile-bar"></div>
<!-- Its own button is inside the overlay and never counts as obscured by it. -->
<div id="cookie" role="region" aria-label="Cookie notice">
We use cookies for statistics. <button id="fo-in-banner" type="button">Accept</button>
</div>
<script>
var chat = document.createElement('div');
chat.id = 'chat';
chat.style.cssText = 'position: fixed; top: 400px; left: 0; width: 200px; height: 64px; background: #222;';
document.body.appendChild(chat);
</script>
</body>
</html>Passing example
The check reports nothing here. Corpus case interaction_focus_obscured, element a#fo-edge. The page may contain further findings for other rules.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Focus hidden behind fixed content</title>
<style>
body { font-family: sans-serif; margin: 0; padding: 480px 16px 16px; color: #111; background: #fff; }
a, button { display: inline-block; min-width: 44px; min-height: 44px; padding: 12px; box-sizing: border-box; margin: 0; }
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 16px; top: 520px; }
.probe { position: absolute; width: 140px; height: 44px; background: #fff; }
#cookie { position: fixed; top: 0; left: 0; right: 0; height: 120px; background: #222; color: #fff; padding: 12px; box-sizing: border-box; }
#cookie button { background: #fff; color: #111; }
#hidden-bar { position: fixed; top: 150px; left: 0; right: 0; height: 60px; background: #222; visibility: hidden; }
#backdrop { position: fixed; top: 220px; left: 0; right: 0; height: 60px; background: #e8eef8; z-index: -1; }
#mobile-bar { display: none; }
@media (max-width: 600px) {
#mobile-bar { display: block; position: fixed; top: 300px; left: 0; right: 0; height: 60px; background: #222; }
}
</style>
</head>
<body>
<a class="skip" href="#main">Skip to main content</a>
<header><p style="margin: 0">Example Insurance</p></header>
<nav aria-label="Main">
<a href="/" aria-current="page">Home</a>
<a href="/claims">Claims</a>
</nav>
<main id="main">
<!-- Controls placed at fixed spots of the first screen, so each meets exactly one overlay. -->
<!-- needs_review (2.4.11 and 2.4.12): entirely under the cookie banner -->
<a id="fo-covered" class="probe" href="/privacy" style="top: 40px; left: 24px;">Privacy</a>
<!-- needs_review (2.4.12 only): 20 of 44 px under the banner -->
<a id="fo-partial" class="probe" href="/imprint" style="top: 100px; left: 24px;">Imprint</a>
<!-- absent: 1 px edge contact, within the rounding tolerance -->
<a id="fo-edge" class="probe" href="/contact" style="top: 119px; left: 180px;">Contact</a>
<!-- absent: the bar above it is visibility: hidden -->
<a id="fo-under-hidden" class="probe" href="/jobs" style="top: 160px; left: 24px;">Jobs</a>
<!-- absent: the fixed backdrop paints below the link (z-index: -1) -->
<a id="fo-above-backdrop" class="probe" href="/press" style="top: 230px; left: 24px;">Press</a>
<!-- needs_review (2.4.11 and 2.4.12) on the mobile viewport only -->
<a id="fo-mobile" class="probe" href="/app" style="top: 310px; left: 24px;">Get the app</a>
<!-- needs_review (2.4.11 and 2.4.12): covered by a chat widget a script adds after load -->
<a id="fo-dynamic" class="probe" href="/support" style="top: 410px; left: 24px;">Support</a>
<h1>Make a claim</h1>
<p>Report damage online or <a href="/call">call us</a>.</p>
</main>
<footer><p>Example Insurance footer</p></footer>
<div id="hidden-bar"></div>
<div id="backdrop"></div>
<div id="mobile-bar"></div>
<!-- Its own button is inside the overlay and never counts as obscured by it. -->
<div id="cookie" role="region" aria-label="Cookie notice">
We use cookies for statistics. <button id="fo-in-banner" type="button">Accept</button>
</div>
<script>
var chat = document.createElement('div');
chat.id = 'chat';
chat.style.cssText = 'position: fixed; top: 400px; left: 0; width: 200px; height: 64px; background: #222;';
document.body.appendChild(chat);
</script>
</body>
</html>This page is generated from the rule catalogue and the rule contracts; see therule overview for how the outcome classes are derived.