target-size-minimum

Pointer targets must be at least 24x24px large, or leave sufficient distance to other targets

automaticWCAG 2.5.8 (AA)confidence: mediumseverity: seriousoperable

Checks that pointer-operable targets have an effective hit region of at least 24 by 24 CSS pixels, or meet an allowed exception (e.g. sufficient spacing).

Applies to. Applies to <button>, <summary>, <a href>, <area href>, <input>, <select>, <textarea> and elements with role="button"/"link" that are pointer-reachable: rendered, not suppressed by pointer-events:none, and with a measurable box of non-zero size. Accessibility-tree exclusion isn't a filter here: an aria-hidden control is still a target a pointer can hit. <area> is matched but never actually evaluated, for the reason given below.

Expectation. Each target is at least 24 by 24 CSS pixels, or meets one of the SC 2.5.8 exceptions this rule can establish from geometry: spacing (a 24px-diameter circle centred on the target reaches no unrelated target), the inline exception for a link inside a run of text, or user-agent sizing (an unstyled native checkbox or radio, detected by appearance not having been reset to none). An undersized target too close to a neighbour fails. Where an exception may apply but geometry cannot confirm it (two inline links in one run of text, or a target inside an SVG, canvas or image map that may be essential), the result is cantTell rather than a guess. Notes (engine intent):

  • This rule is DOM-based and measures pointer hit regions available to sighted pointer users.
  • Elements can be "pointer-operable" even if excluded from the accessibility tree (e.g. aria-hidden="true").
  • Excludes targets that are not pointer-reachable due to rendering suppression (display:none, etc.), or pointer suppression (pointer-events:none), or zero geometry (e.g. scale(0) -> zero rects).

WCAG 2.5.8 exceptions implemented, and how:

  • Spacing: a 24px-diameter circle centered on an undersized target must not intersect another (unrelated) target's box or another undersized target's own circle. Two passes: a fast center-distance check (exact for undersized-vs-undersized, a reasonable proxy otherwise) and a 16-point perimeter sample via elementFromPoint as a more precise fallback for cases the distance check under-detects (e.g. a small target adjacent to a large, elongated neighbor). Ancestor/descendant relationships between the target and the "other" element are never treated as a conflict (see isRelated): a nested-interactive shape, a small control inside its own wrapping link/button, is one visual region, not two independent targets. That pattern is nested-interactive-controls-absent's concern, not a spacing one.
  • Inline: a link inside a text-block container passes outright (isInlineTextExceptionTarget). An inline link whose only spacing conflict is another inline link in the same run is reported as cantTell (isInlineLinkTarget), since the inline exception may cover it but geometry can't confirm that.
  • User Agent Control: an unstyled native checkbox/radio, detected via `appearance` not being reset to `none` (see isUserAgentSizedControl). Scoped narrowly to checkbox/radio specifically, not every form control, since those are the only types with unambiguous native rendering.
  • Essential/Equivalent: only a narrow, high-confidence subset is asserted (SVG/canvas/map-embedded controls, see isPlausiblyEssentialOrEquivalent); anything else defers to cantTell rather than guessing "essential" from a layout container.

Known gap, left unimplemented on purpose: `<area>` (image-map hotspot) elements are not evaluated at all. `area[href]` is in CANDIDATE_SELECTOR for forward-compatibility, but it's currently a no-op. `<area>` has no CSS box of its own (`display: none` by the HTML spec's default UA stylesheet, confirmed against the spec rather than a jsdom quirk), so `getBoundingClientRect()` always reports zero geometry and `isPointerReachable`'s existing `display:none` check rejects it before any size/exception logic runs. A real `<area>` hit-region is computed by the browser from its `shape`/`coords` attributes against the associated `<img>`'s *rendered* size, an entirely different measurement path than every other candidate here. Implementing that properly (parsing `coords`, resolving the owning `<img>` via its `usemap`, accounting for the image's CSS-scaled render size) is a separate, larger feature, not attempted in this pass. This is an automatic, deterministic approximation intended to be:

  • strict on clear failures,
  • conservative when exceptions cannot be determined reliably.

Examples

pass
<button style="width:24px; height:24px;">+</button>

Meets the 24×24 CSS pixel minimum on its own.

fail
<button style="width:10px; height:10px;">A</button>
<button style="width:10px; height:10px; margin-left:5px;">B</button>

Both targets are under 24×24 and closer together than the spacing exception allows.

View source