skills/fixing-accessibility-issues/SKILL.md
Use this skill to fix accessibility issues in implemented UI code. Triggers when remediating WCAG violations, fixing audit findings, resolving axe-core errors, implementing accessible patterns for the first time, or when a coding agent needs guidance choosing between ARIA approaches, focus management strategies, or accessible markup patterns. For reviewing code without fixing it, use `reviewing-accessibility` instead.
npx skillsauth add mattobee/skills fixing-accessibility-issuesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fix accessibility issues in implemented UI code. This skill takes findings from reviewing-accessibility, axe-core scans, or manual audits and produces code changes. It does not find new issues, estimate effort, prioritise, or write tests — use reviewing-accessibility, estimating-accessibility-effort, prioritising-accessibility-fixes, and writing-accessibility-tests for those.
Each finding should include at minimum:
Findings may come from reviewing-accessibility output, axe-core violation reports, browser DevTools accessibility audits, or manual testing notes. If a finding lacks a file location, search the codebase to locate the affected code before proceeding.
Before changing anything, gather context:
package.json, imports, and surrounding code.For each finding:
role="dialog" requires focus trapping, focus return, Escape handling, and aria-modal.aria-live to an element that updates frequently can overwhelm screen reader users.<div> to <button> may change styling, event handling, and form submission behaviour.When multiple valid approaches exist, use these defaults. Alternatives are noted where they have a clear advantage.
Default: visible text content. A <button> should contain text that names it.
Fall back to these in order, only when visible text is not viable:
aria-labelledby pointing to an existing visible text element — keeps the name in sync with what sighted users see.sr-only / visually-hidden CSS class) inside the element — translatable, discoverable in DOM.aria-label — last resort. Invisible, easy to forget during updates, not translatable by default in many i18n setups.For <img>, always use the alt attribute. For decorative images, use alt="". For <svg>, use <title> with aria-labelledby, or role="img" with aria-label if no visible title is appropriate.
Default: let the browser handle focus. Only manage focus programmatically when the default behaviour is wrong.
Situations that require programmatic focus:
Use tabindex="-1" on non-interactive elements that need to receive programmatic focus. Remove it from elements that should not be in the tab order.
Default: use semantic HTML that conveys state natively. A <details> element conveys expanded/collapsed without ARIA. A <button disabled> conveys disabled state. A checked <input type="checkbox"> conveys checked state.
When ARIA is needed:
aria-expanded on the trigger element, not the content panel.aria-current="page" on navigation links pointing to the current page.aria-invalid="true" on form fields with validation errors, paired with aria-describedby pointing to the error message. Prefer aria-describedby over aria-errormessage — the latter has inconsistent assistive technology support.aria-live="polite" for status updates. Use "assertive" only for urgent errors. Never put aria-live on an element that updates more than once every few seconds.aria-modal="true" on dialog elements, paired with role="dialog". The native <dialog> element with showModal() handles this automatically.Default: associate error messages with inputs using aria-describedby.
aria-invalid="true".id should be referenced by aria-describedby on the input.Colour contrast fixes require changing either the foreground or background colour.
Default: fix the heading to match the page-level hierarchy, not the component's internal view of itself.
A component that renders an <h2> may need to become an <h3> when placed inside a section that already has an <h2>. If the component is used in multiple contexts, accept the heading level as a prop or use aria-level on a <div role="heading">.
Do not skip heading levels. Do not remove headings to "fix" hierarchy — add the missing intermediate levels instead, or adjust the existing ones.
Default: use native interactive elements. A <button> is keyboard accessible. A <div onClick> is not.
When replacing a non-interactive element with an interactive one:
<button> for actions, <a href> for navigation.<div> or <span> must remain (rare — usually because of styling constraints in a third-party system), add role="button", tabindex="0", and key handlers for Enter and Space.For custom widgets (tabs, comboboxes, tree views, menus), follow the keyboard patterns in the WAI-ARIA Authoring Practices Guide. Do not invent custom key bindings.
Default: add an aria-live region to the DOM on page load, then update its content when the status changes. An aria-live region added dynamically may not be tracked by all assistive technologies.
aria-live="polite" for non-urgent updates (search result counts, save confirmations, filter feedback).aria-live="assertive" only for errors requiring immediate attention.role="status" (implicitly aria-live="polite") for status messages.role="alert" (implicitly aria-live="assertive") for error alerts.Some findings should be left alone:
alt="". Flagging these as "missing alt text" is a false positive.When leaving a finding unfixed, note the reason in the output so the decision is traceable.
After making fixes:
Do not report a fix as complete until validation passes.
aria-label is the last resort, not the first. Agents default to aria-label because it is the simplest attribute to add. Prefer visible text, aria-labelledby, or visually hidden text — they are more robust, more translatable, and more likely to stay in sync with the visible UI.<h3> to an <h2> in one component may create a skip or duplicate at the page level. Always check the full page heading outline, not just the component in isolation.role="dialog" without focus trapping creates a worse problem. A dialog role without focus management lets screen reader users navigate behind the dialog into content they cannot see. If the full modal pattern (trap, return, escape, inert background) is not feasible, do not add the role.aria-live regions must exist in the DOM before content changes. Adding an aria-live region and immediately populating it may not trigger an announcement. The region should be present (and empty) on page load.<div> with a <button> changes more than keyboard access. It changes the element's default styling, its behaviour inside forms (it can submit), and its interaction with CSS frameworks. Check for visual regressions and unintended form submissions after the swap.role, aria-*, or tabindex to a component library's wrapper element can conflict with the library's own accessibility implementation. Check the library's API for built-in accessibility props before adding manual ARIA.aria-errormessage. Support for aria-errormessage is inconsistent across assistive technologies. Use aria-describedby for error message association instead.tabindex="0" to non-interactive elements. Making headings, paragraphs, or containers focusable via keyboard confuses users who expect only interactive elements in the tab order. Use tabindex="-1" if the element only needs to receive programmatic focus.Ground all fixes in the official WCAG specification and the ARIA Authoring Practices Guide. When a fix involves ARIA, check the role, state, or property against the WAI-ARIA spec. When uncertain, consult the spec rather than relying on convention.
tools
Use this skill to work through review feedback on a pull request — read the inline review comments, assess each one's validity, make the code changes that are warranted, and reply to every thread with a one-line explanation of what was done (or why it was declined). Triggers when the user asks to address PR feedback, respond to reviewers, work through review comments, handle a code review, action the comments on a PR, or asks "what do the reviewers want changed?" Also triggers when resuming work on a PR that has open review threads.
testing
Use this skill to write Playwright accessibility tests using the two-layer strategy (axe-core scans + targeted assertions). Triggers when adding accessibility test coverage, reviewing test gaps, writing axe scans, or creating Playwright assertions for accessible names, landmarks, ARIA states, focus management, or contrast.
tools
Use this skill to suggest prioritised next steps for a project. Triggers when the user asks what to work on next, wants to resume after a break, or needs help prioritising a backlog.
development
Use this skill to review implemented UI code for WCAG accessibility compliance. Triggers when reviewing components, pages, or templates for accessibility, auditing a feature after implementation, or answering questions about accessible patterns, ARIA, keyboard navigation, or screen reader support.