# Accessibility Audit

> Reviews web and application accessibility against WCAG 2.2 AA and the European Accessibility Act (EAA, enforceable June 2025). Covers semantic HTML, ARIA usage, keyboard navigation, focus management, color contrast, screen-reader compatibility, form labels and errors, alternative text, motion and animation preferences, internationalization and localization quality, and testing tool integration (a…

- **Type:** Skill
- **Install:** `agentstack add skill-nordic-ai-production-readiness-skills-accessibility-audit`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Nordic-AI](https://agentstack.voostack.com/s/nordic-ai)
- **Installs:** 0
- **Category:** [Cloud & Infrastructure](https://agentstack.voostack.com/c/cloud-infrastructure)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [Nordic-AI](https://github.com/Nordic-AI)
- **Source:** https://github.com/Nordic-AI/production-readiness-skills/tree/main/skills/accessibility-audit

## Install

```sh
agentstack add skill-nordic-ai-production-readiness-skills-accessibility-audit
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Accessibility Audit

You review the application's accessibility — whether people with disabilities can use it. The standard is WCAG 2.2 Level AA; for products sold in the EU, the European Accessibility Act (EAA) makes this legally required from June 2025 for a broad category of products and services.

This skill follows the library-wide rules in [`docs/CONVENTIONS.md`](../../docs/CONVENTIONS.md). Read that first.

## Scope

Applies whenever the application has a user interface — web, mobile web, desktop Electron, native mobile if the codebase is visible. Does not apply to backend-only services or internal CLIs (but see release-readiness for operator tooling).

## Inputs

From orchestrator: `scope_tier`, `jurisdiction`, `stack_summary`, `gitnexus_indexed`. Plus:

- `ui_framework`: react | vue | angular | svelte | ember | plain-html | ios-native | android-native | flutter | other
- `target_wcag_level`: A | AA | AAA (default AA)
- `eaa_in_scope`: true | false — does the product fall under the EAA? (e-commerce, banking, transport tickets, e-books, computing hardware + OS, telecoms, AV media services, ATMs, etc.)

If not provided, ask.

## Finding ID prefix

`A11Y` — see `CONVENTIONS.md` §4.

## Tier thresholds

| Tier | WCAG A | WCAG AA | EAA applicable | Auto-testing | Manual screen-reader testing |
|---|---|---|---|---|---|
| prototype | advisory | advisory | advisory | optional | optional |
| team | **required** | **required for user-facing flows** | **required if applicable** | **required** in CI | recommended |
| scalable | **required** | **required everywhere** | **required if applicable + audited** | **required** with zero-regression gate | **required** per major release |

## Review surface

### 1. Semantic HTML

Semantic elements carry accessibility for free. Misused ``/`` is the most common accessibility failure.

- `` for clickable actions, not ``. The former is keyboard-focusable, has proper role, fires on Enter/Space.
- `` for navigation, not `` or `` with `onClick={() => navigate(...)}`.
- Landmark elements: ``, ``, ``, ``, `` — one `` per page.
- Heading hierarchy ``–`` with no skips.
- Form structure: `` tied to ``, `` + `` for grouped inputs.
- Lists: `` / `` for lists, not series of ``.
- Tables: `` with ``, ``, `` for data tables. Never for layout.
- `` for modal dialogs (or well-implemented ARIA dialog pattern), not just a styled ``.

### 2. Keyboard navigation

Every interactive element must be reachable and operable by keyboard.

- Tab order follows visual order (no surprising jumps).
- All custom interactive components (`role="button"`, `role="menuitem"`, etc.) handle keyboard — Enter/Space for activation, Arrow keys for navigation in composite widgets, Escape for dismissal.
- Focus visible — no `outline: none` without a replacement focus style (WCAG 2.4.7 Focus Visible).
- No keyboard traps — can always Tab out of every component.
- Skip links — "Skip to main content" for repetitive navigation.
- Modal dialogs trap focus inside while open and restore focus on close.
- Drag-and-drop has a keyboard-accessible alternative (WCAG 2.5.7).

Grep for anti-patterns:
- `tabindex="-1"` on things users need to interact with.
- `tabindex="99"` etc. — positive tabindex disrupts natural order.
- `onClick` on `` / `` without corresponding `onKeyDown` and `role="button"` + `tabindex="0"`.
- `outline: none` / `outline: 0` without `:focus-visible` replacement.

### 3. ARIA usage

ARIA is a prosthesis, not an enhancement. Rule of ARIA: "No ARIA is better than bad ARIA."

- **First rule**: if you can use a native HTML element, use it — don't reach for ARIA.
- `aria-label` / `aria-labelledby` for elements that need a label but can't have a visible one (icon buttons).
- `aria-describedby` for supplementary info (e.g. form hints, error messages).
- `aria-live` regions for dynamic updates — `polite` for most, `assertive` only for interruptions.
- `aria-hidden="true"` only for decorative content; never on focusable elements (breaks the accessibility tree relationship).
- `aria-expanded`, `aria-selected`, `aria-checked` states on composite widgets.
- Complex widgets follow the WAI-ARIA Authoring Practices patterns (combobox, tabs, accordion, menu, treeview).

Common anti-patterns:
- `aria-label` repeating visible text verbatim (redundant announcement).
- Missing `role` on custom widgets.
- `aria-hidden` on the entire page / `` while a modal is open (should only hide siblings).
- Applying `role="button"` without full keyboard support.

### 4. Color and contrast

- **WCAG 1.4.3** — text contrast ratio at least 4.5:1 (AA) or 7:1 (AAA) for normal text; 3:1 (AA) or 4.5:1 (AAA) for large text (≥18pt or 14pt bold).
- **WCAG 1.4.11** — non-text UI component contrast at least 3:1 (borders of inputs, icons conveying meaning, focus indicators).
- **Color not the only channel of information** (WCAG 1.4.1) — don't rely on red/green alone for error/success. Pair with icon, text, shape.
- Dark-mode parity — contrast checked in both themes.
- Tools: `axe-core`, Chrome DevTools contrast checker, Stark, WebAIM contrast checker.

### 5. Text and content

- Resizable to 200% (WCAG 1.4.4) without loss of functionality — avoid fixed pixel sizes on text / container dimensions that clip text.
- Reflow at 320 CSS pixels width (WCAG 1.4.10) — layouts must adapt, no horizontal scrolling for body content.
- Line height ≥ 1.5× font size; paragraph spacing ≥ 2× font size; letter spacing ≥ 0.12× font size; word spacing ≥ 0.16× font size — when user adjusts text spacing (WCAG 1.4.12).
- Plain language where possible; reading level disclosed for complex content.

### 6. Images, icons, media

- **Alt text** on images:
  - Informative images: describe the information the image conveys.
  - Decorative images: `alt=""` or CSS background + `role="presentation"`.
  - Functional images (image-only buttons, links): describe the action, not the picture.
  - Complex images (charts, diagrams): longer description available via `aria-describedby` or adjacent text.
  - Avoid `alt="image of ..."` — screen readers already announce it's an image.
- **Icons**: `aria-label` for standalone icon buttons; `aria-hidden="true"` on decorative icons accompanied by visible text.
- **SVG**: `` inside for accessible name, or `aria-label` on `` if appropriate.
- **Video**:
  - Captions for all pre-recorded audio (WCAG 1.2.2).
  - Audio description for visual information in video (WCAG 1.2.3 / 1.2.5).
  - Transcripts for audio-only media.
  - Controls are keyboard-operable.
  - Autoplay muted or user-initiated; auto-playing audio longer than 3s has a mechanism to pause/mute (WCAG 1.4.2).
- **Live media**: captions (WCAG 1.2.4) for live audio at AA.

### 7. Forms

- Every input has a `` — not just `placeholder` text (disappears on input, low contrast).
- Required fields marked visually and programmatically (`aria-required="true"` or `required`).
- Error messages:
  - Announced to screen readers (`aria-live="polite"` region or `aria-invalid` + `aria-describedby` pointing to the error).
  - Associated with the failing input.
  - Clear and actionable — "Please enter a valid email address" not "Invalid input".
  - Preserved across submits — don't clear the field the user mistyped in.
- `autocomplete` attributes for common fields (WCAG 1.3.5) — `name`, `email`, `tel`, `street-address`, etc.
- Input types appropriate — `type="email"`, `type="tel"`, `type="date"` for better mobile keyboards + semantics.
- Error prevention for critical actions (WCAG 3.3.4) — confirm, review, or undo for legal / financial submissions.
- Focus moves to first error on submit failure.

### 8. Dynamic content

- Content changes announced via `aria-live`, not requiring users to detect them.
- Loading states communicated (`aria-busy`, loading indicators with accessible names).
- Toast notifications use `role="status"` or `role="alert"` depending on severity, and don't auto-dismiss before slow readers can process them.
- Infinite scroll has a "load more" fallback or proper virtualization that doesn't break screen readers.

### 9. Motion and sensory

- **Prefers-reduced-motion** respected (WCAG 2.3.3 AAA, but broadly expected at AA level of polish):
  ```css
  @media (prefers-reduced-motion: reduce) { ... }
  ```
- No content flashes more than 3 times per second (WCAG 2.3.1) — photosensitive epilepsy trigger.
- Parallax / scroll-linked animations have a preference-based opt-out.
- Autoplay animations / carousels have pause/stop/hide controls (WCAG 2.2.2).
- No information conveyed by sound / motion alone.

### 10. Time limits

- Session timeouts: user warned before, given time to extend (WCAG 2.2.1).
- Re-authenticate preserves user's in-progress work.
- Time-limited interactions (e.g. checkout with limited-duration cart) provide extension / disable option.

### 11. Navigation and orientation

- Page title unique and descriptive (WCAG 2.4.2).
- Multiple ways to reach a page (WCAG 2.4.5) — nav, search, sitemap.
- Focus order meaningful (WCAG 2.4.3).
- Link text descriptive out of context (WCAG 2.4.4) — "Read more about X" not "click here".
- Current location indicated in nav.
- Breadcrumbs for deep hierarchies.
- Language declared at document level (``) and on lang-switching elements (`bonjour`) — WCAG 3.1.1 / 3.1.2.

### 12. Mobile / touch

- Target size ≥ 24×24 CSS pixels (WCAG 2.5.8 AA in WCAG 2.2, ≥ 44×44 advisory).
- Tap targets with adequate spacing.
- No drag-only interactions without alternative (WCAG 2.5.7).
- Pointer cancelation (WCAG 2.5.2) — down-event alone shouldn't trigger critical actions; allow cancel on up-event.
- Orientation not locked (WCAG 1.3.4) — works in both portrait and landscape unless essential.

### 13. Authentication

WCAG 2.2 added 3.3.8 Accessible Authentication (Minimum) at AA:

- Don't require a cognitive function test (memorizing a string, transcribing from an image) unless there's an alternative (e.g. copy-paste allowed, password managers allowed, third-party auth).
- CAPTCHAs have an alternative form — or, where possible, replace with hCaptcha/Turnstile silent challenges.

### 14. Screen reader experience

Auto-testing catches maybe 30% of issues. Manual screen reader testing is required at team+ tier for user-facing flows.

- **Tools**: NVDA (Windows, free), JAWS (Windows, commercial), VoiceOver (macOS, iOS, built-in), TalkBack (Android, built-in), Narrator (Windows).
- **Flows to test**:
  - Signup / login
  - Core transaction (purchase, submit, send)
  - Error paths
  - Modal dialogs
  - Dynamic content updates
- **What to listen for**:
  - Announcements make sense out of visual context.
  - No "unlabeled button" / "link".
  - Reading order matches logical order.
  - Focused element's state communicated (expanded/collapsed, selected, disabled).

### 15. Internationalization (i18n) accessibility

- Strings externalized, not baked into JSX/HTML as literals that block translation.
- RTL support (Arabic, Hebrew) — `dir="rtl"`, logical CSS properties (`margin-inline-start` not `margin-left`).
- Plurals / gender handled by the i18n library, not string concatenation.
- Date / number / currency formatting locale-aware.
- Icon and imagery culturally appropriate.

### 16. Testing tooling

- **Automated** in CI:
  - `axe-core` (via `jest-axe`, `@axe-core/playwright`, `cypress-axe`, `pa11y`).
  - `Lighthouse` accessibility score as a CI gate (target ≥ 95 AA).
  - `eslint-plugin-jsx-a11y` for React projects.
  - Framework-specific: `@angular-eslint/eslint-plugin-template`, Vue `a11y` eslint plugins.
- **Manual** at team+ tier:
  - Keyboard-only pass per release.
  - Screen reader pass per release.
  - Zoom to 200% / 400% pass.
- **User testing** with disabled users at scalable tier.

### 17. Accessibility statement and feedback

- **Accessibility statement** published (required under EAA and many public-sector procurement rules). Describes: conformance level, known issues + remediation timeline, alternative access routes, contact for accessibility feedback, date of last review.
- **Feedback channel** monitored — users can report barriers.

## Category enum (for findings)

- `semantic-html`
- `keyboard`
- `aria`
- `contrast`
- `text-scaling`
- `media`
- `forms`
- `dynamic-content`
- `motion`
- `time-limit`
- `navigation`
- `mobile-touch`
- `authentication`
- `screen-reader`
- `i18n`
- `tooling`
- `statement`

## Severity guidance

| Level | Examples |
|---|---|
| critical | Critical flow (signup, checkout, submit) impossible with keyboard or screen reader. No alt text on functional images in primary flows. Color the only channel for a critical distinction (e.g. error vs success) in a payment flow. |
| high | Form errors not announced. Contrast failing AA on interactive elements. Focus indicator missing. Modal missing focus trap. |
| medium | Alt text present but unhelpful. Headings not hierarchical. Language attribute missing. Autoplay carousel without pause. |
| low | Minor ARIA redundancy. Decorative icons without `aria-hidden`. |
| info | Observations or WCAG AAA items not targeted. |

## Example findings

### Example 1 — Keyboard-inaccessible card with click handler

```yaml
- id: A11Y-003
  severity: high
  category: keyboard
  title: "Product card uses div+onClick, unreachable by keyboard"
  location: "src/components/ProductCard.tsx:24"
  description: |
    ProductCard wraps its content in a `` that navigates
    to the product detail page on click. The element has no tabindex, no
    role, and no keyboard handler. Keyboard users cannot open product
    details, which blocks the core browse-to-purchase flow. VoiceOver
    reports the element as a group, so screen reader users also cannot
    activate it.
  evidence:
    - |
      // src/components/ProductCard.tsx:24
      return (
         navigate(`/p/${slug}`)}>
          
          {title}
          {price}
        
      );
  remediation:
    plan_mode: |
      Replace the click-on-div with a semantic ``
      wrapping the card content. Adjust CSS so the link doesn't inherit
      default underline behavior but retains focus-visible styling. Also
      fix the empty-alt image: provide product name alt, or keep empty if
      the title beneath is sufficient (prefer the latter to avoid repeat).
    edit_mode: |
      Proposed diff replaces div+onClick with ; adjusts card CSS;
      removes redundant onClick handler. Safe — purely structural change.
  references:
    - "WCAG 2.1.1 Keyboard"
    - "WCAG 4.1.2 Name, Role, Value"
    - "WAI-ARIA Authoring Practices — don't use div for navigation"
  wcag_success_criterion: "2.1.1"
  blocker_at_tier: [team, scalable]
```

### Example 2 — Error messages not associated with inputs

```yaml
- id: A11Y-008
  severity: high
  category: forms
  title: "Signup form errors displayed visually but not announced to screen readers"
  location: "src/components/SignupForm.tsx:55-80"
  description: |
    The signup form shows validation errors in a red box next to each field,
    but the errors aren't associated with their inputs: no `aria-invalid`,
    no `aria-describedby` link, no live region. Screen reader users submit,
    hear nothing, and have no way to discover why submit failed. Testing
    with VoiceOver confirms: focus stays on the Submit button, which is now
    disabled, with no announcement.
  evidence:
    - |
      // src/components/SignupForm.tsx:66
      
      {errors.email && {errors.email}}
  remediation:
    plan_mode: |
      1. Add `aria-invalid={!!errors.email}` to each input.
      2. Give each error `id="email-error"` and add
         `aria-describedby="email-error"` to the input when error exists.
      3. Add a top-of-form `` that summarizes errors on
         submit failure; move focus to this region or to the first
         errored input.
      4. Keep visual red styling — it still serves sighted users.
    edit_mode: |
      Proposed: refactor input component to accept error prop and wire
      aria attributes automatically. Changes apply to all for

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Nordic-AI](https://github.com/Nordic-AI)
- **Source:** [Nordic-AI/production-readiness-skills](https://github.com/Nordic-AI/production-readiness-skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-nordic-ai-production-readiness-skills-accessibility-audit
- Seller: https://agentstack.voostack.com/s/nordic-ai
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
