{"id":"accessibility","name":"accessibility","summary":"WCAG 2.2ガイドラインに従い、ウェブアクセシビリティの監査と改善を行う。「アクセシビリティ向上」、「a11y監査」、「WCAG準拠」、「スクリーンリーダーサポート」、「キーボードナビゲーション」、「アクセシブル化」などの要請に応じて使用してください。","body":"# Accessibility (a11y)\n\nComprehensive accessibility guidelines based on WCAG 2.2 and Lighthouse accessibility audits. Goal: make content usable by everyone, including people with disabilities.\n\n## WCAG Principles: POUR\n\n| Principle | Description |\n|-----------|-------------|\n| **P**erceivable | Content can be perceived through different senses |\n| **O**perable | Interface can be operated by all users |\n| **U**nderstandable | Content and interface are understandable |\n| **R**obust | Content works with assistive technologies |\n\n## Conformance levels\n\n| Level | Requirement | Target |\n|-------|-------------|--------|\n| **A** | Minimum accessibility | Must pass |\n| **AA** | Standard compliance | Should pass (legal requirement in many jurisdictions) |\n| **AAA** | Enhanced accessibility | Nice to have |\n\n---\n\n## Perceivable\n\n### Text alternatives (1.1)\n\n**Images require alt text:**\n```html\n<!-- ❌ Missing alt -->\n<img src=\"chart.png\">\n\n<!-- ✅ Descriptive alt -->\n<img src=\"chart.png\" alt=\"Bar chart showing 40% increase in Q3 sales\">\n\n<!-- ✅ Decorative image (empty alt) -->\n<img src=\"decorative-border.png\" alt=\"\" role=\"presentation\">\n\n<!-- ✅ Complex image with longer description -->\n<figure>\n  <img src=\"infographic.png\" alt=\"2024 market trends infographic\" \n       aria-describedby=\"infographic-desc\">\n  <figcaption id=\"infographic-desc\">\n    <!-- Detailed description -->\n  </figcaption>\n</figure>\n```\n\n**Icon buttons need accessible names:**\n```html\n<!-- ❌ No accessible name -->\n<button><svg><!-- menu icon --></svg></button>\n\n<!-- ✅ Using aria-label -->\n<button aria-label=\"Open menu\">\n  <svg aria-hidden=\"true\"><!-- menu icon --></svg>\n</button>\n\n<!-- ✅ Using visually hidden text -->\n<button>\n  <svg aria-hidden=\"true\"><!-- menu icon --></svg>\n  <span class=\"visually-hidden\">Open menu</span>\n</button>\n```\n\n**Visually hidden class:**\n```css\n.visually-hidden {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  white-space: nowrap;\n  border: 0;\n}\n```\n\n### Color contrast (1.4.3, 1.4.6)\n\n| Text Size | AA minimum | AAA enhanced |\n|-----------|------------|--------------|\n| Normal text (< 18px / < 14px bold) | 4.5:1 | 7:1 |\n| Large text (≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 |\n| UI components & graphics | 3:1 | 3:1 |\n\n```css\n/* ❌ Low contrast (2.5:1) */\n.low-contrast {\n  color: #999;\n  background: #fff;\n}\n\n/* ✅ Sufficient contrast (7:1) */\n.high-contrast {\n  color: #333;\n  background: #fff;\n}\n\n/* ✅ Focus states need contrast too (3:1 against background, WCAG 1.4.11) */\n:focus-visible {\n  outline: 2px solid currentColor;\n  outline-offset: 2px;\n}\n```\n\n**Don't rely on color alone:**\n```html\n<!-- ❌ Only color indicates error -->\n<input class=\"error-border\">\n<style>.error-border { border-color: red; }</style>\n\n<!-- ✅ Color + icon + text -->\n<div class=\"field-error\">\n  <input aria-invalid=\"true\" aria-describedby=\"email-error\">\n  <span id=\"email-error\" class=\"error-message\">\n    <svg aria-hidden=\"true\"><!-- error icon --></svg>\n    Please enter a valid email address\n  </span>\n</div>\n```\n\n### Media alternatives (1.2)\n\n```html\n<!-- Video with captions -->\n<video controls>\n  <source src=\"video.mp4\" type=\"video/mp4\">\n  <track kind=\"captions\" src=\"captions.vtt\" srclang=\"en\" label=\"English\" default>\n  <track kind=\"descriptions\" src=\"descriptions.vtt\" srclang=\"en\" label=\"Descriptions\">\n</video>\n\n<!-- Audio with transcript -->\n<audio controls>\n  <source src=\"podcast.mp3\" type=\"audio/mp3\">\n</audio>\n<details>\n  <summary>Transcript</summary>\n  <p>Full transcript text...</p>\n</details>\n```\n\n---\n\n## Operable\n\n### Keyboard accessible (2.1)\n\n**All functionality must be keyboard accessible.** Prefer native interactive elements — `<button>`, `<a href>`, and form controls handle Enter/Space activation, focus, and assistive-tech semantics for free. Only add manual keyboard handling when you cannot use a native element.\n\n```html\n<!-- ❌ Non-interactive element with click only: not focusable, no keyboard activation -->\n<div class=\"card\" onclick=\"handleAction()\">Open</div>\n\n<!-- ✅ Best: use a native button -->\n<button type=\"button\" onclick=\"handleAction()\">Open</button>\n```\n\n```javascript\n// ✅ When you MUST use a non-interactive element (e.g. div with role=\"button\"),\n// make it focusable AND handle keyboard activation. Do NOT add this to a native\n// <button> — Enter/Space already fire click, so you'd double-trigger.\nelement.setAttribute('role', 'button');\nelement.setAttribute('tabindex', '0');\nelement.addEventListener('click', handleAction);\nelement.addEventListener('keydown', (e) => {\n  if (e.key === 'Enter' || e.key === ' ') {\n    e.preventDefault();\n    handleAction();\n  }\n});\n```\n\n**No keyboard traps.** Users must be able to Tab into and out of every component. Use the [modal focus trap pattern](references/A11Y-PATTERNS.md#modal-focus-trap) for dialogs—the native `<dialog>` element handles this automatically.\n\n### Focus visible (2.4.7)\n\n```css\n/* ❌ Never remove focus outlines */\n*:focus { outline: none; }\n\n/* ✅ Use :focus-visible for keyboard-only focus */\n:focus {\n  outline: none;\n}\n\n:focus-visible {\n  outline: 2px solid currentColor; /* inherits text color → already contrast-checked */\n  outline-offset: 2px;\n}\n\n/* ✅ Or pick a brand color and verify ≥3:1 contrast against every background it lands on */\nbutton:focus-visible {\n  box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);\n}\n```\n\n### Focus not obscured (2.4.11) — new in 2.2\n\nWhen an element receives keyboard focus, it must not be entirely hidden by other author-created content such as sticky headers, footers, or overlapping panels. At Level AAA (2.4.12), no part of the focused element may be hidden.\n\n```css\n/* ✅ Account for sticky headers when scrolling to focused elements */\n:target {\n  scroll-margin-top: 80px;\n}\n\n/* ✅ Ensure focused items clear fixed/sticky bars */\n:focus {\n  scroll-margin-top: 80px;\n  scroll-margin-bottom: 60px;\n}\n```\n\n### Skip links (2.4.1)\n\nProvide a skip link so keyboard users can bypass repetitive navigation. See the [skip link pattern](references/A11Y-PATTERNS.md#skip-link) for full markup and styles.\n\n### Target size (2.5.8) — new in 2.2\n\nInteractive targets must be at least **24 × 24 CSS pixels** (AA). Exceptions: inline text links, elements where the browser controls the size, and targets where a 24px circle centered on the bounding box does not overlap another target.\n\n```css\n/* ✅ Minimum target size */\nbutton,\n[role=\"button\"],\ninput[type=\"checkbox\"] + label,\ninput[type=\"radio\"] + label {\n  min-width: 24px;\n  min-height: 24px;\n}\n\n/* ✅ Comfortable target size (recommended 44×44) */\n.touch-target {\n  min-width: 44px;\n  min-height: 44px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n}\n```\n\n### Dragging movements (2.5.7) — new in 2.2\n\nAny action that requires dragging must have a single-pointer alternative (e.g., buttons, inputs). See the [dragging movements pattern](references/A11Y-PATTERNS.md#dragging-movements) for a sortable-list example.\n\n### Timing (2.2)\n\n```javascript\n// Allow users to extend time limits\nfunction showSessionWarning() {\n  const modal = createModal({\n    title: 'Session Expiring',\n    content: 'Your session will expire in 2 minutes.',\n    actions: [\n      { label: 'Extend session', action: extendSession },\n      { label: 'Log out', action: logout }\n    ],\n    timeout: 120000\n  });\n}\n```\n\n### Motion (2.3)\n\n```css\n/* Respect reduced motion preference */\n@media (prefers-reduced-motion: reduce) {\n  *,\n  *::before,\n  *::after {\n    animation-duration: 0.01ms !important;\n    animation-iteration-count: 1 !important;\n    transition-duration: 0.01ms !important;\n    scroll-behavior: auto !important;\n  }\n}\n```\n\n---\n\n## Understandable\n\n### Page language (3.1.1)\n\n```html\n<!-- ❌ No language specified -->\n<html>\n\n<!-- ✅ Language specified -->\n<html lang=\"en\">\n\n<!-- ✅ Language changes within page -->\n<p>The French word for hello is <span lang=\"fr\">bonjour</span>.</p>\n```\n\n### Consistent navigation (3.2.3)\n\n```html\n<!-- Navigation should be consistent across pages -->\n<nav aria-label=\"Main\">\n  <ul>\n    <li><a href=\"/\" aria-current=\"page\">Home</a></li>\n    <li><a href=\"/products\">Products</a></li>\n    <li><a href=\"/about\">About</a></li>\n  </ul>\n</nav>\n```\n\n### Consistent help (3.2.6) — new in 2.2\n\nIf a help mechanism (contact info, chat widget, FAQ link, self-help option) is repeated across multiple pages, it must appear in the **same relative order** each time. Users who rely on consistent placement shouldn't have to hunt for help on every page.\n\n### Form labels (3.3.2)\n\nEvery input needs a programmatically associated label. See the [form labels pattern](references/A11Y-PATTERNS.md#form-labels) for explicit, implicit, and instructional examples.\n\n### Error handling (3.3.1, 3.3.3)\n\nAnnounce errors to screen readers with `role=\"alert\"` or `aria-live`, set `aria-invalid=\"true\"` on invalid fields, and focus the first error on submit. See the [error handling pattern](references/A11Y-PATTERNS.md#error-handling) for full markup and JS.\n\n### Redundant entry (3.3.7) — new in 2.2\n\nDon't force users to re-enter information they already provided in the same session. Auto-populate from earlier steps, or let users select from previously entered values. Exceptions: security re-confirmation and content that has expired.\n\n```html\n<!-- ✅ Auto-fill shipping address from billing -->\n<fieldset>\n  <legend>Shipping address</legend>\n  <label>\n    <input type=\"checkbox\" id=\"same-as-billing\" checked>\n    Same as billing address\n  </label>\n  <!-- Fields auto-populated when checked -->\n</fieldset>\n```\n\n### Accessible authentication (3.3.8) — new in 2.2\n\nLogin flows must not rely on cognitive function tests (e.g., remembering a password, solving a puzzle) unless at least one of:\n- A copy-paste or autofill mechanism is available\n- An alternative method exists (e.g., passkey, SSO, email link)\n- The test uses object recognition or personal content (AA only; AAA removes this exception)\n\n```html\n<!-- ✅ Allow paste in password fields -->\n<input type=\"password\" id=\"password\" autocomplete=\"current-password\">\n\n<!-- ✅ Offer passwordless alternatives -->\n<button type=\"button\">Sign in with passkey</button>\n<button type=\"button\">Email me a login link</button>\n```\n\n---\n\n## Robust\n\n### ARIA usage (4.1.2)\n\n**Prefer native elements:**\n```html\n<!-- ❌ ARIA role on div -->\n<div role=\"button\" tabindex=\"0\">Click me</div>\n\n<!-- ✅ Native button -->\n<button>Click me</button>\n\n<!-- ❌ ARIA checkbox -->\n<div role=\"checkbox\" aria-checked=\"false\">Option</div>\n\n<!-- ✅ Native checkbox -->\n<label><input type=\"checkbox\"> Option</label>\n```\n\n**When ARIA is needed,** use the correct roles and states. See the [ARIA tabs pattern](references/A11Y-PATTERNS.md#aria-tabs) for a complete tablist example.\n\n### Live regions (4.1.3)\n\nUse `aria-live` regions to announce dynamic content changes without moving focus. See the [live regions pattern](references/A11Y-PATTERNS.md#live-regions-and-notifications) for markup and a `showNotification()` helper.\n\n---\n\n## Testing checklist\n\n### Automated testing\n```bash\n# Lighthouse accessibility audit\nnpx lighthouse https://example.com --only-categories=accessibility\n\n# axe-core\nnpm install @axe-core/cli -g\naxe https://example.com\n```\n\n### Manual testing\n\n- [ ] **Keyboard navigation:** Tab through entire page, use Enter/Space to activate\n- [ ] **Screen reader:** Test with VoiceOver (Mac), NVDA (Windows), or TalkBack (Android)\n- [ ] **Zoom:** Content usable at 200% zoom\n- [ ] **High contrast:** Test with Windows High Contrast Mode\n- [ ] **Reduced motion:** Test with `prefers-reduced-motion: reduce`\n- [ ] **Focus order:** Logical and follows visual order\n- [ ] **Target size:** Interactive elements meet 24×24px minimum\n\nSee the [screen reader commands reference](references/A11Y-PATTERNS.md#screen-reader-commands) for VoiceOver and NVDA shortcuts.\n\n---\n\n## Common issues by impact\n\n### Critical (fix immediately)\n1. Missing form labels\n2. Missing image alt text\n3. Insufficient color contrast\n4. Keyboard traps\n5. No focus indicators\n\n### Serious (fix before launch)\n1. Missing page language\n2. Missing heading structure\n3. Non-descriptive link text\n4. Auto-playing media\n5. Missing skip links\n\n### Moderate (fix soon)\n1. Missing ARIA labels on icons\n2. Inconsistent navigation\n3. Missing error identification\n4. Timing without controls\n5. Missing landmark regions\n\n## References\n\n- [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/)\n- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)\n- [Deque axe Rules](https://dequeuniversity.com/rules/axe/)\n- [Web Quality Audit](../web-quality-audit/SKILL.md)\n- [WCAG criteria reference](references/WCAG.md)\n- [Accessibility code patterns](references/A11Y-PATTERNS.md)","author":"@addyosmani","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/addyosmani/web-quality-skills/tree/main/skills/accessibility","license":"MIT","category":"review","lang":"en","tokens":3316,"stars":0,"calls30d":2,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"references/A11Y-PATTERNS.md","size":6036,"sha256":"f10e238dd4dd3c4f51134c2056bc02d3dd4f8fb6ccc9145c2c1ba7aab4d58140"},{"path":"references/WCAG.md","size":9079,"sha256":"16683e025e3833d931c9d880645d98a448827f97e10189b6c8f12c832e7a2361"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["dequeuniversity.com","external.com","wave.webaim.org","www.deque.com","www.nvaccess.org","www.tpgi.com"]}}