auditmysitev2.0.2

Scrollable region not reachable by keyboard

keyboard/scrollable-region-not-focusable

WCAG success criterion
2.1.1 (level A)
Outcome class
Automatic. An automated check that can report a confirmed violation.
Area
Accessibility, Navigation & Operation
Default severity
high
axe-core rule
scrollable-region-focusable

Why we flag this

Keyboard users cannot scroll the region and cannot read the hidden content.

Element with overflow: auto/scroll and overflowing content, without tabindex and without focusable children.

What the rule checks

Scope. Rendered elements other than html/body whose content overflows their box and that contain text.

Expectation. violation once per scroll region whose content extends more than 13 px beyond its box (scroll overflow), with text to reveal, when neither the region (tabindex >= 0) nor any non-inert element inside can take keyboard focus: the hidden part stays unreachable from the keyboard. Overflow of 13 px or less is sub-pixel or padding noise and passes; overflow: hidden is no scroll region; a region without text, an inert region and a region that is not rendered are not reported. Overflow on one viewport only (text wrapping on the phone) counts, as does content a script adds after load.

Source. WCAG 2.2 SC 2.1.1 Keyboard; axe-core scrollable-region-focusable (13 px tolerance)

Detection: shared; needs dom, geometry, computed_style.

Failing example

The check reports a violation here. Corpus case scrollable_region_focusable. The page may contain further findings for other rules.

scrollable_region_focusable.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Scrollable regions and keyboard access</title>
    <style>
        .box { height: 60px; overflow: auto; border: 1px solid #333; }
        .tall { height: 400px; }
    </style>
</head>
<body>
    <main>
        <h1>Scrollable regions (#717)</h1>

        <!-- violation: scrolls, not focusable, nothing focusable inside -->
        <div class="box menus"><div class="tall">Only text that needs scrolling to read.</div></div>

        <!-- pass: the region itself is focusable -->
        <div class="box" tabindex="0" role="region" aria-label="Terms"><div class="tall">Terms text.</div></div>

        <!-- pass: focusable content inside scrolls the region along -->
        <div class="box"><div class="tall">Links: <a href="#a">first link</a></div></div>

        <!-- pass: overflow hidden, not a scroll region -->
        <div class="box" style="overflow: hidden"><div class="tall">Clipped text.</div></div>

        <!-- pass: does not overflow -->
        <div class="box">Short text.</div>
    </main>
</body>
</html>

Passing example

The check passes here and reports nothing. Corpus case interaction_scroll_regions, element div#sr-tolerance. The page may contain further findings for other rules.

interaction_scroll_regions.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Scrollable regions and keyboard access</title>
    <style>
        body { font-family: sans-serif; margin: 0; padding: 16px; color: #111; background: #fff; }
        a, button { display: inline-block; min-width: 44px; min-height: 44px; padding: 12px; box-sizing: border-box; margin: 4px; }
        .skip { position: absolute; left: -9999px; }
        .skip:focus { left: 16px; }
        .box { height: 100px; overflow: auto; border: 1px solid #333; margin: 8px 0; padding: 0; }
        .inner { margin: 0; padding: 0; }
        /* One line on a wide screen, many lines on a phone: overflows on the mobile viewport only. */
        .narrow-box { height: 40px; overflow: auto; border: 1px solid #333; margin: 8px 0; }
    </style>
</head>
<body>
    <a class="skip" href="#main">Skip to main content</a>
    <header><p>Example Library</p></header>
    <nav aria-label="Main">
        <a href="/" aria-current="page">Home</a>
        <a href="/catalogue">Catalogue</a>
    </nav>
    <main id="main">
        <h1>Loan conditions</h1>

        <!-- violation: 30 px beyond the box, not focusable, nothing focusable inside -->
        <div id="sr-over" class="box"><div class="inner" style="height: 130px">Loan conditions text that needs scrolling.</div></div>
        <!-- pass: 10 px beyond the box, within the 13 px tolerance for sub-pixel layout -->
        <div id="sr-tolerance" class="box"><div class="inner" style="height: 110px">Opening hours text.</div></div>
        <!-- pass: overflows, but the region is focusable and named -->
        <div id="sr-focusable" class="box" tabindex="0" role="region" aria-label="Fees"><div class="inner" style="height: 300px">Fees text.</div></div>
        <!-- pass: overflows, but a link inside takes focus and scrolls it along -->
        <div id="sr-link" class="box"><div class="inner" style="height: 300px">Rules, see <a href="#rules">all rules</a>.</div></div>
        <!-- pass: overflows, but the only focusable element inside is inert -->
        <div id="sr-inert" class="box" inert><div class="inner" style="height: 300px">Archived conditions.</div></div>
        <!-- not reported: overflows, but has no text to reveal -->
        <div id="sr-empty" class="box"><div class="inner" style="height: 300px"></div></div>
        <!-- not reported: not rendered -->
        <div id="sr-hidden" class="box" style="display: none"><div class="inner" style="height: 300px">Hidden text.</div></div>
        <!-- violation on the mobile viewport only: the text wraps into many lines -->
        <div id="sr-mobile" class="narrow-box"><p class="inner">Members can borrow up to ten items at a time, renew each item twice online, and return items at any branch of the library network without extra charge. Overdue items cost ten cents a day, and reserved items wait at the counter for one week.</p></div>
        <!-- violation: filled by a script after load -->
        <div id="sr-dynamic" class="box"></div>
    </main>
    <footer><p>Example Library footer</p></footer>
    <script>
        var filler = document.createElement('div');
        filler.style.height = '300px';
        filler.textContent = 'Reservation history loaded after the page.';
        document.getElementById('sr-dynamic').appendChild(filler);
    </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.