accessibility-linter
| Author | Som |
| Version | 1.0 |
| License | MIT |
| When to use | While actively designing or building a UI component |
| Input | Component description, design intent, HTML / React code |
| Output | Structural challenges and open questions, not a compliance report |
| Covers | Semantic HTML, ARIA, keyboard navigation, focus management, component states, color contrast, motion, touch targets |
| Not this | A WCAG audit, a linter plugin, a post-ship review tool |
Bad accessibility is a structural problem wearing an accessibility costume. When focus order is wrong, the hierarchy is wrong. When a button needs aria-label, the visual design isn't doing its job. When a modal traps keyboard users, the interaction model was never finished.
Don't list what's wrong. Ask the structural question behind the failure. Push back on decisions that haven't been made explicit. That's the point.
Fix the structure. The a11y fix follows.
What every a11y failure is really saying
Every accessibility failure is an underspecified design decision. Use this as your diagnostic lens: find the failure, then ask the structural question behind it.
| a11y failure | structural question to ask |
|---|---|
| Broken focus order | Does the DOM order actually match the visual hierarchy? |
| Unlabelled interactive element | Was the action named, or only implied visually? |
| Color as the only state signal | Was state modeled, or just styled? |
| Icon-only button with no label | Does the visual shorthand have a verbal equivalent? |
Missing alt text | Was the image's meaning in context ever defined? |
| Focus trap with no exit | Does the interaction model have a close path? |
| Dynamic content not announced | Was the state change treated as communication, or just a visual update? |
| Placeholder as label | Was input context provided, or assumed? |
| Disabled button with no explanation | Is this a dead end, or a gate? Does the user know the difference? |
| Touch target too small | Did the interaction model assume precision the user doesn't have? |
aria-label on a native element | Why wasn't the visual design doing this work? |
| Missing focus state | Was focus ever part of the design, or added at the end? |
How this skill thinks
There are two tiers of accessibility work. Treat them differently.
Tier 1: Build it in, don't ask
Some accessibility decisions aren't decisions. They're defaults. When writing any code, include these without being prompted. They're part of what "finished" means:
<button>not<div onClick>.<a href>not<span onClick>.aria-labelon every icon-only button. Always.<label>paired with every<input>.placeholderis not a label.alton every image. Empty string for decorative ones.aria-invalid="true"+ error message viaaria-describedbyon form errors.aria-busy="true"on containers while loading.aria-expanded="true/false"on every disclosure trigger.role="status"oraria-live="polite"on dynamic content regions.role="alert"on error messages.prefers-reduced-motionrespected on anything that animates.- Visible focus styles. Never
outline: nonewithout a replacement. disabledattribute on disabled native elements, not just visual opacity.- Landmark elements:
<nav>,<main>,<footer>. Not<div>all the way down.
If you add something non-obvious, say it briefly in passing: "I'm using aria-describedby to link the helper text to the input." Then move on. Don't make it a lesson unless asked.
Tier 2: Ask before building
Some decisions have structural consequences that can't be patched later. If any of these are ambiguous before the build starts, ask one focused question and wait for an answer before writing code.
- Custom keyboard navigation: tabs, combobox, menu, tree view, date picker. These have defined patterns. Confirm which pattern before building the interaction.
- Focus management: when something opens, where does focus go? When it closes, where does it return? If this isn't clear, ask.
- Modal vs. non-modal: does this overlay trap focus or not? A modal traps focus and needs
Escapeto close. A popover or tooltip does not. These are architecturally different. Decide before building. - Live region strategy: if content updates frequently (real-time data, polling), does every update need to be announced? Constant announcements are as bad as none. Decide the cadence.
- Complex ARIA patterns: if the component requires
role="combobox",role="grid", orrole="treegrid", align on the pattern before implementing. These patterns are specific and unforgiving if done wrong. - Intentional deviations from default patterns: see below.
Ask one question. Not a list of questions. The most important unresolved decision, stated plainly:
"Before I build this: does the filter panel open as a modal (focus trap,
Escapeto close) or as an inline expansion? That changes the implementation significantly."
Intentional deviations: when breaking the default is right
Some accessibility conventions exist as defaults, not absolutes. When the UX stakes are high enough, overriding them is the correct call, but only if the deviation is deliberate, communicated, and has an explicit alternative.
The pattern: default behavior exists, UX intent conflicts with it, surface the tension, decide consciously, handle the alternative.
Common cases worth raising before building:
-
Disabling
Escapeand click-outside on a modal: correct for multi-step flows, complex forms, or any modal where accidental dismissal causes data loss. The modal must have an explicit close or cancel action, and if there's unsaved state, the user needs to be told before they leave. -
Capturing
Tabin an editor or custom input: a code editor or rich text field that uses Tab for indentation is correct to do so, but keyboard users need an escape hatch (EscapethenTab, or a documented shortcut). If Tab is captured with no alternative, keyboard-only users are trapped. -
Blocking form submission until a selection is made: valid for comboboxes and typeaheads where free text isn't allowed. The constraint needs to be communicated before the user tries to submit, not only after they fail.
-
Preventing scroll or navigation during a flow: acceptable in multi-step wizards or onboarding sequences where losing context is genuinely disorienting. The user always needs a visible way out, even if it abandons progr