WCAG 2.2 CSS Lint - Focus & Target Size
Fourteen findings, eleven success criteria, sixty-one lines of CSS. That is what came back from one stylesheet of the kind a frontend developer at an EU SaaS ships every week - a pricing card, a sticky header, a spinner, an .sr-only class. Three of those fourteen findings are the interesting ones, because they name criteria that did not exist in WCAG 2.1: 2.5.8 Target Size (Minimum), 2.4.11 Focus Not Obscured (Minimum) and 2.4.13 Focus Appearance. WCAG 2.2 became a W3C Recommendation on 5 October 2023 and added five criteria; three of them are decided almost entirely in CSS. A lint config written against 2.1 - or a code assistant trained mostly on 2.1-era stylesheets - returns nothing for them. The rules pass review, and the criteria fail. Here is what the file said, and what the criterion says back. .icon-btn { width: 20px; height: 20px; } - 2.5.8 AA. A pointer target has to be at least 24 by 24 CSS px unless it has 24px of clear space around it. Twenty is the number an icon font wants; twenty-four is the number the criterion wants. .site-header { position: sticky; top: 0; height: 64px; } - 2.4.11 AA. Tab down the page and the browser scrolls the focused element to the top of the viewport, which is where the sticky bar is. The element has focus and nobody can see it. The fix is one declaration - scroll-padding-top - somewhere in the file, and the file did not have it. *:focus { outline: none; } - 2.4.7 AA, still the most common line in this list. It is only safe when :focus-visible draws a ring back, and this file had .btn:focus { outline: 1px dotted } instead, which is itself 2.4.13: a focus ring has to be at least 2px. The rest are older criteria that CSS keeps breaking anyway: line-height 1.35 on body text and letter-spacing locked with !important (1.4.12 Text Spacing), min-width 1024px on the body (1.4.10 Reflow), text-size-adjust: none and a root font-size pinned to 14px (1.4.4 Resize Text), .sr-only implemented with display: none, which hides it from the screen reader it was written for (1.3.1), a spinner set to infinite with no way to stop it (2.2.2), and a file that animates without ever answering prefers-reduced-motion (2.3.3). Why not just run axe or Lighthouse? Because they inspect a rendered page. They need a route that renders, they report a DOM node rather than the declaration that caused it, and a rule sitting in a file that no current route renders is never reached at all. A stylesheet lint runs on the source, in the editor, before the route exists. Why now: the European Accessibility Act has applied since 28 June 2025, and enforcement is national. The cost of finding this at the end rather than in the editor is an accessibility consultant's $150-$250 hour, or a third-party audit quoted in the thousands. The extension is 14 rules, and each finding is printed with its criterion number, its conformance level and its line. Linting the file you have open is free and complete - no watermark, no counter, no cap on runs. The licence key covers a different job: sweeping every stylesheet in the workspace at once and exporting a dated criterion-by-criterion table, the paper record that sits behind an accessibility statement. The same engine, with the same 14 rules, also runs as a page in the browser with nothing installed. CSS is one layer, and these rules only claim that layer - a finding is a place to look with the criterion already identified, not a conformance claim. But it is the layer where 2.5.8, 2.4.11 and 2.4.13 are won or lost, and right now it is the layer nothing is reading. Free in your browser (the same rules): https://getreadystack.com/tools/wcag22-css-lint Licence ($29, once, 7-day refund): https://buy.polar.sh/polar_cl_gYGJTycG04gELmMeEpKE2BMsam4llOPlJb69i33u33l Top comments (0)
Comments
No comments yet. Start the discussion.