Skip to main content

accessibility

Web accessibility (a11y). WCAG 2.1, ARIA, keyboard navigation, screen readers, testing tools.

Source facts

Repository
skeletorflet/opencode-kit
Last source activity
April 3, 2026 at 15:32
Detected SKILL.md language
English
Stars
9
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
accessibility
description
Web accessibility (a11y). WCAG 2.1, ARIA, keyboard navigation, screen readers, testing tools.
# Accessibility (a11y) > Accessible apps are better apps for everyone. --- ## 1. WCAG 2.1 Core Principles (POUR) | Principle | Meaning | |-----------|---------| | **Perceivable** | Info must be presentable to all senses | | **Operable** | UI must be keyboard + assistive tech operable | | **Understandable** | Content and operation must be comprehensible | | **Robust** | Must work with current + future assistive tech | **Target: AA conformance** (minimum for most products) --- ## 2. Color & Contrast | Element | Minimum Ratio | AA+ Ratio | |---------|-------------|-----------| | Normal text (< 18px) | 4.5:1 | 7:1 | | Large text (≥ 18px) | 3:1 | 4.5:1 | | UI components (borders, icons) | 3:1 | — | | Disabled state | No requirement | — | ``` Tools: ├── WebAIM Contrast Checker ├── Chrome DevTools → Accessibility panel └── axe DevTools browser extension ``` --- ## 3. Semantic HTML First ```html \x3C!-- ❌ Bad --> <div class="button" onclick="submit()">Submit</div> <div class="heading">Section Title</div> <div class="nav">...</div> \x3C!-- ✅ Good --> <button type="submit">Submit</button> <h2>Section Title</h2> <nav aria-label="Main navigation">...</nav> ``` --- ## 4. ARIA Patterns ```html \x3C!-- When to use ARIA: Rule 1: Don\'t use ARIA if native HTML works Rule 2: Don\'t change native semantics unnecessarily Rule 3: All interactive ARIA controls must be keyboard-focusable --> \x3C!-- Modal Dialog --> <div role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-desc"> <h2 id="modal-title">Confirm Delete</h2> <p id="modal-desc">This action cannot be undone.</p> <button>Cancel</button> <button>Delete</button> </div> \x3C!-- Live Region (dynamic content) --> <div aria-live="polite" aria-atomic="true"> \x3C!-- Updates announced to screen readers --> {statusMessage} </div> \x3C!-- Icon button --> <button aria-label="Close dialog"> <svg aria-hidden="true">...</svg> </button> \x3C!-- Loading --> <div role="status" aria-label="Loading users..."> <Spinner /> </div> ``` --- ## 5. Keyboard Navigation | Key | Expected Behavior | |-----|------------------| | `Tab` | Move to next focusable element | | `Shift+Tab` | Move to previous | | `Enter` / `Space` | Activate button/link | | `Escape` | Close modal/dropdown | | Arrow keys | Navigate within widget (menu, tabs, listbox) | ```tsx // Focus trap (modals) import { useFocusTrap } from "@mantine/hooks"; // or write custom function Modal({ isOpen, onClose, children }) { const focusTrapRef = useFocusTrap(isOpen); return isOpen ? ( <div ref={focusTrapRef} role="dialog" aria-modal="true"> {children} </div> ) : null; } // Skip link (keyboard shortcut to main content) <a href="#main-content" className="sr-only focus:not-sr-only"> Skip to main content </a> ``` --- ## 6. Images & Media ```html \x3C!-- Informative image --> <img src="chart.png" alt="Bar chart showing 40% increase in Q4 revenue" /> \x3C!-- Decorative image --> <img src="divider.png" alt="" role="presentation" /> \x3C!-- Icon (standalone meaning) --> <svg aria-label="Settings" role="img">...</svg> \x3C!-- Video --> <video> <track kind="captions" src="captions.vtt" srclang="en" label="English" /> </video> ``` --- ## 7. Form Accessibility ```html \x3C!-- Always associate label with input --> <label for="email">Email address</label> <input id="email" type="email" required aria-describedby="email-hint email-error" aria-invalid="true" /> <span id="email-hint">We\'ll never share your email.</span> <span id="email-error" role="alert">Please enter a valid email.</span> \x3C!-- Fieldset for groups --> <fieldset> <legend>Notification preferences</legend> <label><input type="checkbox" name="email"> Email</label> <label><input type="checkbox" name="sms"> SMS</label> </fieldset> ``` --- ## 8. Testing Tools | Tool | Type | Use For | |------|------|---------| | **axe DevTools** | Browser ext | Automated scan | | **Lighthouse** | Chrome DevTools | Audit score | | **NVDA** | Screen reader (Windows) | Manual testing | | **VoiceOver** | Screen reader (Mac/iOS) | Manual testing | | **jest-axe** | Unit test | CI/CD integration | | **Playwright** | E2E | Keyboard/focus testing | ```ts // jest-axe import { axe, toHaveNoViolations } from "jest-axe"; expect.extend(toHaveNoViolations); it("has no accessibility violations", async () => { const { container } = render(<MyForm />); const results = await axe(container); expect(results).toHaveNoViolations(); }); ``` --- ## 9. Quick Checklist - [ ] Color contrast meets AA (4.5:1 normal text) - [ ] Keyboard navigable (Tab, Enter, Escape, arrows) - [ ] All images have meaningful alt text - [ ] Forms have visible labels (not just placeholders) - [ ] Focus indicators visible (don\'t `outline: none`) - [ ] Skip navigation link on every page - [ ] Error messages associated with fields - [ ] Page has one `<h1>`, logical heading hierarchy - [ ] Interactive elements have 44×44px touch target minimum
View on GitHub