auditmysitev2.0.2

Insufficient click target size

targets/size

WCAG success criterion
2.5.8 (level AA)
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
target-size-minimum

Why we flag this

Users with hand tremors or on mobile devices easily tap the wrong elements.

Links, buttons or form controls dimensioned too small in CSS.

What the rule checks

Scope. Rendered, clickable pointer targets (links with href, buttons, fields, elements with a widget role or in the tab order) that are not inert, not pointer-events: none and not moved off-screen.

Expectation. needs_review once per target smaller than 24 x 24 CSS px whose 24 px circle, centred on the target, intersects another target or the circle of another undersized target (the spacing exception fails). 24 px passes, 23 px fails; centres 30 px apart pass, 20 px apart fail. Not reported: links in running text (inline), a checkbox or radio whose <label> enlarges the target (an aria-label does not), a target with a visible same-destination equivalent of 24 px, targets not rendered or invisible. Native and ARIA targets count alike; a size reached on the phone viewport only, or set by script after load, counts. A target still growing when measured is UNTESTED (needs_review).

Source. WCAG 2.2 SC 2.5.8 Target Size (Minimum): 24 x 24 CSS px, exceptions Spacing, Equivalent, Inline, User agent control, Essential

Detection: shared; needs dom, geometry, computed_style.

Failing example

The check flags this for review. Corpus case target_size_animation, element a#slow. The page may contain further findings for other rules.

target_size_animation.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Target size during animation corpus case</title>
    <style>
        .row { display: flex; gap: 4px; margin: 48px 0; align-items: flex-start; }
        .row a { display: block; flex: none; height: 48px; overflow: hidden; white-space: nowrap; line-height: 48px; }
        .big { width: 48px; }
        /* A logo intro that grows from nothing, like geographia.eu's module name (#706). */
        @keyframes grow { from { width: 0; } to { width: 120px; } }
        #intro { width: 120px; animation: grow 1s ease-in; }
        @keyframes grow-slow { from { width: 12px; } to { width: 120px; } }
        #slow { width: 120px; animation: grow-slow 3600s linear; }
        @keyframes spin { to { transform: rotate(360deg); } }
        .spinner { width: 16px; height: 16px; border: 2px solid; animation: spin 1s linear infinite; }
    </style>
</head>
<body>
    <main>
        <h1>Targets measured while they animate (#706)</h1>
        <!-- An infinite animation never settles; it must not hold up or suppress anything. -->
        <div class="spinner" aria-hidden="true"></div>

        <!-- pass: grows from 0 to 120x48 over 1 s; the audit waits for the animation to finish
             and measures the final size. Measured mid-animation it would be undersized, with a
             neighbour 4 px away. -->
        <div class="row">
            <a id="intro" href="/intro">Atmosphere</a>
            <a class="big" href="/intro-next">Next</a>
        </div>

        <!-- pass (not failed, needs review): still growing after the settle budget, so its size is
             reported as not measured instead of as a violation -->
        <div class="row">
            <a id="slow" href="/slow">Slow intro</a>
            <a class="big" href="/slow-next">Next</a>
        </div>
    </main>
</body>
</html>

Passing example

The check reports nothing here. Corpus case target_size_animation, element a#intro. The page may contain further findings for other rules.

target_size_animation.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Target size during animation corpus case</title>
    <style>
        .row { display: flex; gap: 4px; margin: 48px 0; align-items: flex-start; }
        .row a { display: block; flex: none; height: 48px; overflow: hidden; white-space: nowrap; line-height: 48px; }
        .big { width: 48px; }
        /* A logo intro that grows from nothing, like geographia.eu's module name (#706). */
        @keyframes grow { from { width: 0; } to { width: 120px; } }
        #intro { width: 120px; animation: grow 1s ease-in; }
        @keyframes grow-slow { from { width: 12px; } to { width: 120px; } }
        #slow { width: 120px; animation: grow-slow 3600s linear; }
        @keyframes spin { to { transform: rotate(360deg); } }
        .spinner { width: 16px; height: 16px; border: 2px solid; animation: spin 1s linear infinite; }
    </style>
</head>
<body>
    <main>
        <h1>Targets measured while they animate (#706)</h1>
        <!-- An infinite animation never settles; it must not hold up or suppress anything. -->
        <div class="spinner" aria-hidden="true"></div>

        <!-- pass: grows from 0 to 120x48 over 1 s; the audit waits for the animation to finish
             and measures the final size. Measured mid-animation it would be undersized, with a
             neighbour 4 px away. -->
        <div class="row">
            <a id="intro" href="/intro">Atmosphere</a>
            <a class="big" href="/intro-next">Next</a>
        </div>

        <!-- pass (not failed, needs review): still growing after the settle budget, so its size is
             reported as not measured instead of as a violation -->
        <div class="row">
            <a id="slow" href="/slow">Slow intro</a>
            <a class="big" href="/slow-next">Next</a>
        </div>
    </main>
</body>
</html>

This page is generated from the rule catalogue and the rule contracts; see therule overview for how the outcome classes are derived.