Sixty-two buttons are 22×22, two pixels under the minimum #115

Closed
opened 2026-09-07 18:54:30 +00:00 by tiagoagueda · 1 comment
Owner

Measured

Every focusable element on seven pages, checked against WCAG 2.2 SC 2.5.8 Target Size
(Minimum)
-- level AA, 24×24 CSS pixels -- with the standard exemptions applied
(inline links in running text, and the spacing exception where a 24 px circle centred on the
target touches nothing else):

Count What Size Verdict
62 button.btn-ghost.p-1 22×22 fails -- crowded by its neighbours
37 input[type=checkbox] 13×13 probably not -- see below
8 a "See the board" and its like 20 high fails on height
26 input[type=radio] 13×13 exempt by spacing
7 a.skip-link 1×1 exempt -- full size when focused

The 62 are the real one, and they are two pixels short

btn-ghost p-1 gives a 16-pixel icon three pixels of padding: 22×22, against a minimum of
24. One padding step -- p-1 to p-1.5 -- puts every one of them over the line. They are
also the icon-only controls in every table row and every menu, so they are the ones a person
on a touch screen aims at most often.

The checkboxes need a person, not a script

13×13 is the browser's default, and the measurement called them crowded because another
checkbox sits within 24 px. But SC 2.5.8 measures the target, and where a
<label for=...> activates the control the label is part of it -- which would make these
pass. Postulo's checkboxes do have labels. So this row is most likely a false positive of
my own measurement
, recorded rather than quietly dropped: somebody should confirm the
label is clickable in each case before anything is changed.

Why axe passes

axe does not implement 2.5.8. It is a measurement of a rendered box rather than a property
of the markup.

Worth adding with the fix

The measurement itself, as a test: every focusable element on the pages the suite already
visits, against 24×24 with the exemptions. About thirty lines, and it is what stops the next
p-1 button.

Classification

Accessibility, bug. Tier 3: real, level AA, and small -- two pixels on one utility class for
the bulk of it.

## Measured Every focusable element on seven pages, checked against **WCAG 2.2 SC 2.5.8 Target Size (Minimum)** -- level **AA**, 24×24 CSS pixels -- with the standard exemptions applied (inline links in running text, and the spacing exception where a 24 px circle centred on the target touches nothing else): | Count | What | Size | Verdict | | --- | --- | --- | --- | | **62** | `button.btn-ghost.p-1` | 22×22 | **fails** -- crowded by its neighbours | | 37 | `input[type=checkbox]` | 13×13 | **probably not** -- see below | | 8 | `a` "See the board" and its like | 20 high | fails on height | | 26 | `input[type=radio]` | 13×13 | exempt by spacing | | 7 | `a.skip-link` | 1×1 | exempt -- full size when focused | ## The 62 are the real one, and they are two pixels short `btn-ghost p-1` gives a 16-pixel icon three pixels of padding: 22×22, against a minimum of 24. One padding step -- `p-1` to `p-1.5` -- puts every one of them over the line. They are also the icon-only controls in every table row and every menu, so they are the ones a person on a touch screen aims at most often. ## The checkboxes need a person, not a script 13×13 is the browser's default, and the measurement called them crowded because another checkbox sits within 24 px. But SC 2.5.8 measures **the target**, and where a `<label for=...>` activates the control the label is part of it -- which would make these pass. Postulo's checkboxes do have labels. So this row is **most likely a false positive of my own measurement**, recorded rather than quietly dropped: somebody should confirm the label is clickable in each case before anything is changed. ## Why axe passes axe does not implement 2.5.8. It is a measurement of a rendered box rather than a property of the markup. ## Worth adding with the fix The measurement itself, as a test: every focusable element on the pages the suite already visits, against 24×24 with the exemptions. About thirty lines, and it is what stops the next `p-1` button. ## Classification Accessibility, bug. Tier 3: real, level AA, and small -- two pixels on one utility class for the bulk of it.
tiagoagueda added this to the 0.3.0 milestone 2026-09-07 18:54:30 +00:00
Author
Owner

Fixed in 5d00d80. Two things in the report above turned out to be wrong, in opposite directions, and both are worth recording.

The 13x13 checkbox row is not the false positive I called it — but not for the reason I assumed. I wrote that it was "most likely a false positive of my own measurement". Measuring the labels instead of the boxes showed they were 19-20 pixels high, so I assumed a genuine failure everywhere. Then the test found otherwise, page by page:

Where Label was Verdict
Column chooser 181x20 failed — the move buttons sit 8px away, so no spacing exception
Settings -> Plugins 77x19, 101x19, 38x19 passed, on spacing — those rows are tall and nothing is near them

So the row was half a false positive. The plugins page was compliant; the column chooser was not. I fixed both, because passing on spacing means one added control in the row silently breaks it, and nothing would have said so.

The icon is size-3.5, not 16 pixels. 14 + 4 + 4 = 22, which is where the "two pixels under" came from. Worth knowing for the next one: the shortfall is in the icon size as much as the padding.

Three failures the report did not have, all found by running the test rather than by looking:

  • The dashboard's shortcut links — 20 high, 8 pixels apart, in a column. Too small and too close, so neither exception applied. The two other short links on that page (All interviews, Add) are fine, with 130 and 169 pixels of clear space; that difference is exactly what the spacing exception is for.
  • The column chooser's own labels, above.
  • Every sortable table header. 12-pixel type on a 16-pixel line, with a filter input directly beneath. This is on every table in the application and was not in the report at all.

The test you asked for is tests/e2e/test_target_size.py. It came out at about 210 lines rather than thirty, because the exceptions are most of the work and each one needed explaining more than implementing. It walks every page the signed-in browser suite already visits — that list is now a shared function rather than a literal inside the axe test, so a page added later is measured as well as inspected — opens each <details> menu one at a time, and holds every box to 24x24 in Python.

I checked it can fail before trusting it: with .tap-target stripped out of the compiled CSS it reports 9 targets across /applications/ and /jobs/companies/, and with it restored, none.

Not covered, and said out loud rather than left implied:

  • One viewport, 1280x900. Narrow layouts rearrange enough that target size there is a separate question, and it belongs with the reflow work (#113) rather than here.
  • At most 3 menus per page. A table's row menus are the same markup repeated; opening all of them would only be slower.
  • The equivalent exception is not implemented — "another control does the same job at full size" needs a person. Nothing relies on it today; anything that ever does goes in EXEMPT with the reason written down.
Fixed in 5d00d80. Two things in the report above turned out to be wrong, in opposite directions, and both are worth recording. **The 13x13 checkbox row is not the false positive I called it — but not for the reason I assumed.** I wrote that it was "most likely a false positive of my own measurement". Measuring the labels instead of the boxes showed they were 19-20 pixels high, so I assumed a genuine failure everywhere. Then the test found otherwise, page by page: | Where | Label was | Verdict | |---|---|---| | Column chooser | 181x20 | **failed** — the move buttons sit 8px away, so no spacing exception | | Settings -> Plugins | 77x19, 101x19, 38x19 | **passed**, on spacing — those rows are tall and nothing is near them | So the row was half a false positive. The plugins page was compliant; the column chooser was not. I fixed both, because passing on spacing means one added control in the row silently breaks it, and nothing would have said so. **The icon is `size-3.5`, not 16 pixels.** 14 + 4 + 4 = 22, which is where the "two pixels under" came from. Worth knowing for the next one: the shortfall is in the icon size as much as the padding. **Three failures the report did not have**, all found by running the test rather than by looking: - The dashboard's shortcut links — 20 high, 8 pixels apart, in a column. Too small *and* too close, so neither exception applied. The two other short links on that page (*All interviews*, *Add*) are fine, with 130 and 169 pixels of clear space; that difference is exactly what the spacing exception is for. - The column chooser's own labels, above. - **Every sortable table header.** 12-pixel type on a 16-pixel line, with a filter input directly beneath. This is on every table in the application and was not in the report at all. **The test you asked for** is `tests/e2e/test_target_size.py`. It came out at about 210 lines rather than thirty, because the exceptions are most of the work and each one needed explaining more than implementing. It walks every page the signed-in browser suite already visits — that list is now a shared function rather than a literal inside the axe test, so a page added later is measured as well as inspected — opens each `<details>` menu one at a time, and holds every box to 24x24 in Python. I checked it can fail before trusting it: with `.tap-target` stripped out of the compiled CSS it reports 9 targets across `/applications/` and `/jobs/companies/`, and with it restored, none. Not covered, and said out loud rather than left implied: - **One viewport, 1280x900.** Narrow layouts rearrange enough that target size there is a separate question, and it belongs with the reflow work (#113) rather than here. - **At most 3 menus per page.** A table's row menus are the same markup repeated; opening all of them would only be slower. - **The *equivalent* exception is not implemented** — "another control does the same job at full size" needs a person. Nothing relies on it today; anything that ever does goes in `EXEMPT` with the reason written down.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#115
No description provided.