WCAG 2.5.8 target size: measure the target, not the element
Every screen had passed an accessibility checker in both themes and a line by line audit. A script that measured found what both had walked past, then made its own mistake, and the mistake is the useful part.
In this piece
Our attendance program, Herewise, had 44 screens. Before anyone outside the studio used it, every one had been audited against a published design checklist, item by item, from screenshots, the source and the running app. axe-core, the accessibility checker, reported nothing on any of its 34 routes, in the light theme or the dark one.
The first real run, with a department’s own roster, came back with a list of faults and two words about the screens: “terrible hierarchy”.
What a script with a ruler found
We wrote a check that loads every route at 1920, 1366, 1280 and 1024 pixels wide and measures instead of looking. On its first run, against screens that had passed both audits, it found:
- a table hiding three of its columns, with nothing to scroll them into view
- sixteen number boxes 1,100 pixels wide, each holding two digits
- a dropdown with no arrow
- seven screens over 12,000 pixels tall; one list of 258 students made its screen 15,349 pixels
- twelve paragraphs running about 170 characters a line
- a two-option toggle stretched to 1,020 pixels across its card
- 34 controls under the 24 by 24 pixel target floor
None of that is subtle, and all of it survived a careful look. The audits by eye found the loud faults: a missing label, a wrong word, a status shown by colour alone. They missed the measurable ones, because a person reading a screen against a checklist is judging it, and a 1,100 pixel box around two digits looks like a design choice until something measures it.
axe-core is not wrong about any of this. A very wide box with a label is valid. A long paragraph passes every contrast rule. Most of that list is not a rule failure at all. It is a layout that nobody measured.
Testing WCAG 2.5.8 target size: not all 34 were real
The target size finding was the one the script got wrong, and the reason is worth more than the count.
It measured each control’s own box with getBoundingClientRect(), which is the
obvious way to measure it. One of our radio buttons is 18 pixels
square, and it was reported as too small. But it sits inside its label, and the
label is about 300 pixels wide. A press anywhere on the label selects it.
WCAG 2.2 defines a target as the “region of the display that will accept a pointer action”. A label that selects its radio button accepts that press. By that definition the target is the label, and the element’s own box says nothing about it.
So the check now asks the page what a press would actually do. For each control it
takes a 24 by 24 box centred on it, and asks document.elementFromPoint() what is
at each corner, then whether that thing would activate the same control:
const activated = (node) => {
const label = node.closest('label');
if (label && label.control) return label.control;
return node.closest('button, a[href], input, select, textarea, [role="button"]');
};
// For each corner of a 24x24 box centred on the control:
const there = document.elementFromPoint(x, y);
const ok = there === null || activated(there) === activated(control);
If all four corners reach the same control, the target is at least 24 by 24, whatever the element’s own size.
That there === null is the second fix. A Remove button straddling the foot of a
1,080 pixel window was called too small, because its lower corners were below the
visible area, past the scrollbar, where elementFromPoint returns nothing. Nothing
under a point means the point is off screen, not that something covers the
control.
Two notes on what this measures. Our floor is stricter than the success criterion: WCAG 2.5.8 lets an undersized target pass if it has enough space around it, and we hold every control to 24 by 24 regardless. And a link inside a sentence is exempt in WCAG, so the check reports those separately rather than counting them.
How 2.5.8 gets measured matters more in Europe than it did a month ago. EN 301 549, the standard that gives a product a presumption of conformity with the European Accessibility Act, was updated to v4.1.1 in September 2026, and it adopts WCAG 2.2, which is where 2.5.8 comes from, in place of 2.1 (AccessibleEU). Until the European Commission cites the new version in its Official Journal, v3.2.1 and WCAG 2.1 remain the reference.
A check has to prove it can fail
Every run now plants three faults of its own before it reports: a 12 pixel button, a box wider than the window, and two buttons laid over each other. If it does not find all three, it exits with an error that says the measurements stopped working, not that the screens are clean.
That rule came from the same week. Our screen checks had defaulted to port 8000, which is also the port the installed program takes. One run, started while the installed copy was open, measured the installed build against a real register and reported a clean sweep of code it had never loaded. We wrote about this kind of check in September, in four tests that could not fail; this was the same fault at the size of a whole audit.
What axe-core cannot find: where focus goes
Just before the program went onto a lecturer’s laptop, every check was green: axe-core, the layout sweep at four widths, a walk that loads every route, and a script that works through the main actions. Then we pressed Enter and Escape by hand, and found four faults on every screen:
- Every Save threw focus to the top of the page. A button is disabled while it works, and Chrome moves focus off a control that becomes disabled. Focus landed on the document itself.
- A dialog opened from another dialog had nothing to return focus to when it closed.
- A redraw after a dialog’s action did the same. The control that had focus was replaced, and focus went with it.
- Form errors were shown but never focused or read out. A keyboard or screen reader user pressed Save and heard nothing.
axe-core reads a page as it stands. It never presses anything, so it cannot see where focus goes after something happens. The fixes went into the shared helpers for busy buttons, dialogs, redraws and form errors, so no single screen can get them wrong again.
One more turned up the same way. A check that asks every twenty seconds whether the fingerprint terminal is connected was rebuilding the whole top bar, search box included, wiping whatever someone was typing. No automated check types into a search box for twenty seconds.
What we changed in how we check
- Measure layout by script, at every width we support, and never read a clean result from a check that has not just found a planted fault.
- Measure a target by what a press activates, not by the element’s box.
- Press every screen from the keyboard after the checker says it is clean, and write down where focus is after each action.
- Give every screen check the address it is checking, and have it print that address.
What this check still misses
- No screen reader pass has been done yet. That is next, with NVDA, and it will find things this did not.
- A 24 pixel floor with no spacing exception is our choice for a program used by lecturers on older laptops, not a reading of what WCAG requires.
Written by Adeboye Oluwatimileyin and Moses-Azuoru George, the two people who are Emberfig. We make money from AdMob inside the Android apps and from setting Herewise up for departments that ask. This site carries no ads yet. Using an app never costs you anything, and we never sell your data.