skills/mav-bp-accessibility/SKILL.md
Accessibility conventions for projects with user-facing interfaces. Covers WCAG 2.1 AA compliance, semantic HTML, keyboard navigation, colour contrast, screen reader support, and automated a11y testing. Applied when building or reviewing user-facing web or mobile applications.
npx skillsauth add thermiteau/maverick mav-bp-accessibilityInstall 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.
Ensure user-facing interfaces are usable by everyone, including people who rely on assistive technology. WCAG 2.1 AA is the baseline, not the ceiling.
<button> is always better than a <div role="button">These standards apply to:
These standards do NOT apply to:
If a project has no user-facing interface, this skill can be skipped entirely.
Before applying these standards, load the project-specific accessibility implementation:
digraph lookup {
"docs/maverick/skills/accessibility/SKILL.md exists?" [shape=diamond];
"Read and use alongside these standards" [shape=box];
"Invoke upskill" [shape=box];
"Read generated skill" [shape=box];
"docs/maverick/skills/accessibility/SKILL.md exists?" -> "Read and use alongside these standards" [label="yes"];
"docs/maverick/skills/accessibility/SKILL.md exists?" -> "Invoke upskill" [label="no"];
"Invoke upskill" -> "Read generated skill";
"Read generated skill" -> "Read and use alongside these standards";
}
docs/maverick/skills/accessibility/SKILL.mddo-upskill skill with:
aria-|role=|alt=|tabIndex|tabindex|sr-only|visually-hidden|a11y|accessible**/*.a11y.*, **/*accessibility*, **/.axe*Use native HTML elements for their intended purpose. Native elements provide built-in keyboard handling, focus management, and screen reader semantics for free.
| Bad | Good | Why |
| --- | ---- | --- |
| <div role="button" tabindex="0" onclick="..."> | <button onclick="..."> | Native keyboard and focus support |
| <span role="link"> | <a href="..."> | Native navigation semantics |
| <div role="checkbox"> | <input type="checkbox"> | Native form behaviour |
| <div role="heading" aria-level="2"> | <h2> | Native document outline |
| <div role="list"><div role="listitem"> | <ul><li> | Native list semantics |
<h1> through <h6>) in logical order --- do not skip levels<header>, <nav>, <main>, <aside>, <footer><section> and <article> to group related content<main> element<title>When ARIA is necessary (custom widgets with no native equivalent):
role="button" on an <a> elementrole="tab", you must implement arrow key navigationaria-label or aria-labelledby for elements without visible textaria-describedby for supplementary descriptionsoutline: none without a replacement/* BAD: Removes focus indicator entirely */
*:focus {
outline: none;
}
/* GOOD: Custom focus indicator that meets contrast requirements */
*:focus-visible {
outline: 2px solid #1a73e8;
outline-offset: 2px;
}
Focus indicators must have a contrast ratio of at least 3:1 against the surrounding background.
| Element | Minimum Ratio | Example | | ------- | ------------- | ------- | | Normal text (< 18pt / < 14pt bold) | 4.5:1 | Body copy, labels, error messages | | Large text (>= 18pt / >= 14pt bold) | 3:1 | Headings, large UI text | | UI components and graphical objects | 3:1 | Buttons borders, form field borders, icons |
alt text: alt="Bar chart showing Q3 revenue up 15%"alt="" (not omitted --- omitting alt entirely is an error)alt text and a longer description (via aria-describedby or adjacent text)alt or aria-label; purely decorative icons use aria-hidden="true"<label for="..."> associated with the input's idaria-required="true" or required<fieldset> and <legend> (e.g., radio button groups, address fields)aria-describedby or aria-errormessagearia-live="polite" or role="alert" for dynamically displayed errors<!-- GOOD: Accessible form field with error -->
<div>
<label for="email">Email address</label>
<input
id="email"
type="email"
aria-required="true"
aria-invalid="true"
aria-describedby="email-error"
/>
<p id="email-error" role="alert">
Email address must include an @ symbol.
</p>
</div>
<!-- BAD: No label, placeholder-only, no error association -->
<div>
<input type="email" placeholder="Email" />
<p style="color: red;">Invalid</p>
</div>
Use ARIA live regions to announce dynamic updates:
<!-- Status messages (non-urgent) -->
<div aria-live="polite" aria-atomic="true">
3 results found
</div>
<!-- Urgent alerts -->
<div role="alert">
Session expires in 2 minutes.
</div>
aria-live="polite" --- announces when the screen reader is idle (search results, status updates)aria-live="assertive" or role="alert" --- interrupts the current announcement (errors, urgent warnings)Integrate accessibility testing into the CI pipeline to catch regressions automatically.
| Tool | Type | Integration | | ---- | ---- | ----------- | | axe-core | Automated rule engine | Jest, Cypress, Playwright, Storybook | | Lighthouse | Audit suite | CI via lighthouse-ci | | pa11y | Automated testing | CI via pa11y-ci | | eslint-plugin-jsx-a11y | Static analysis | ESLint (React projects) |
# Example: axe-core in a test suite
- name: Run accessibility tests
run: npm test -- --grep "a11y"
# Example: Lighthouse CI
- name: Lighthouse accessibility audit
run: lhci autorun --collect.settings.onlyCategories=accessibility
<html lang="en">)Automated tools are necessary but not sufficient. Perform these manual checks:
When reviewing code for user-facing interfaces, flag these patterns:
| Pattern | Issue | Fix |
| ------- | ----- | --- |
| <div onclick="..."> | Non-semantic interactive element | Use <button> or <a> |
| outline: none without replacement | Invisible focus indicator | Add focus-visible style |
| <img> without alt attribute | Missing text alternative | Add descriptive alt or alt="" for decorative |
| <input> without associated <label> | Unlabelled form field | Add <label for="..."> |
| Colour alone indicates state | Inaccessible to colour-blind users | Add icon, text, or pattern |
| tabindex value > 0 | Breaks natural tab order | Use tabindex="0" or -1 only |
| aria-hidden="true" on focusable element | Hidden but still keyboard-reachable | Remove from tab order or remove aria-hidden |
| Auto-playing video or audio | Disruptive, not user-initiated | Require explicit play action |
| Placeholder used as label | Label disappears on input, poor contrast | Add a visible <label> |
| Missing lang attribute on <html> | Screen reader uses wrong language | Add <html lang="en"> (or appropriate language) |
| Custom widget without keyboard handling | Inoperable for keyboard users | Implement full keyboard interaction pattern |
| Error shown only by colour change | Screen readers cannot detect colour | Add text message and aria-describedby |
development
--- name: do-test description: Write or update tests for a code change. Operates in two modes: `unit` (module-scoped, fast, deterministic) and `integration` (crosses module / service / database boundaries). Intended to be invoked once per testable change from inside a do-issue-* or do-epic phase. Mode is required. argument-hint: mode: unit or integration user-invocable: true disable-model-invocation: false --- **Depends on:** mav-bp-unit-testing, mav-bp-integration-testing, mav-local-verificati
development
Implement a focused code change. Use this skill as the wrapper for any implementation work so the Maverick workflow report captures what was done and so the agent applies the project's coding standards before editing. Intended to be invoked once per task from inside a do-issue-* or do-epic phase, not standalone.
testing
How to stack a PR on top of an unmerged sibling branch, and how to retarget it to the repo's default branch once the sibling merges. Prevents orphan-merge incidents when a dependent story is ready before its parent.
development
Claim, lease, heartbeat, and release protocols for when multiple Claude Code instances may act on the same issue or epic concurrently. GitHub labels and marker comments are the coordination surface; local state is a cache.