AgentSkillsCN

accessibility

遵循 WCAG 标准,打造包容性 Web 应用程序。在实现 UI 组件、开展无障碍性审查、添加 ARIA 属性,或当用户询问“a11y”、“屏幕阅读器”、“键盘导航”或“WCAG 合规性”时,可使用此功能。

SKILL.md
--- frontmatter
name: accessibility
description: Build inclusive web applications following WCAG standards. Use when implementing UI components, reviewing accessibility, adding ARIA attributes, or when user asks about "a11y", "screen reader", "keyboard navigation", or "WCAG compliance".
category: frontend
tags: [accessibility, a11y, wcag, aria, screen-reader]

Skill: Accessibility

Build web applications that everyone can use, including people with disabilities.

Semantic HTML

Rules

  • ✅ DO: Use semantic elements (<header>, <nav>, <main>, <article>)
  • ✅ DO: Use proper heading hierarchy (h1 → h2 → h3)
  • ✅ DO: Use <button> for actions, <a> for navigation
  • ❌ DON'T: Use <div> for everything
  • ❌ DON'T: Skip heading levels

Examples

html
<!-- ❌ Bad - div soup -->
<div class="header">
  <div class="nav">
    <div onclick="navigate()">Home</div>
  </div>
</div>
<div class="content">
  <div class="title">Welcome</div>
</div>

<!-- ✅ Good - semantic HTML -->
<header>
  <nav aria-label="Main navigation">
    <a href="/">Home</a>
    <a href="/about">About</a>
  </nav>
</header>
<main>
  <h1>Welcome</h1>
  <article>
    <h2>Latest News</h2>
    <p>Content...</p>
  </article>
</main>
<footer>
  <p>© 2024 Company</p>
</footer>

Keyboard Navigation

Rules

  • ✅ DO: Make all interactive elements focusable
  • ✅ DO: Use visible focus indicators
  • ✅ DO: Support standard keyboard patterns (Tab, Enter, Escape, Arrow keys)
  • ✅ DO: Manage focus when content changes
  • ❌ DON'T: Remove focus outlines without replacement
  • ❌ DON'T: Create keyboard traps

Examples

css
/* ❌ Bad - removes focus indicator */
*:focus {
  outline: none;
}

/* ✅ Good - visible focus indicator */
:focus-visible {
  outline: 2px solid var(--focus-color);
  outline-offset: 2px;
}

/* ✅ Good - skip link */
.skip-link {
  position: absolute;
  top: -40px;
}
.skip-link:focus {
  top: 0;
}
typescript
// ✅ Good - keyboard support for custom component
function Menu({ items }: { items: MenuItem[] }) {
  const [activeIndex, setActiveIndex] = useState(0);

  const handleKeyDown = (e: KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setActiveIndex(i => Math.min(i + 1, items.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setActiveIndex(i => Math.max(i - 1, 0));
        break;
      case 'Escape':
        closeMenu();
        break;
    }
  };

  return (
    <ul role="menu" onKeyDown={handleKeyDown}>
      {items.map((item, index) => (
        <li
          key={item.id}
          role="menuitem"
          tabIndex={index === activeIndex ? 0 : -1}
        >
          {item.label}
        </li>
      ))}
    </ul>
  );
}

ARIA

Rules

  • ✅ DO: Use ARIA to enhance, not replace, semantic HTML
  • ✅ DO: Use landmarks (role="main", role="navigation")
  • ✅ DO: Use live regions for dynamic content
  • ✅ DO: Label all interactive elements
  • ❌ DON'T: Use ARIA incorrectly (worse than no ARIA)
  • ❌ DON'T: Use role="button" on <div> (use <button>)

Examples

html
<!-- ✅ Good - labeled form -->
<form>
  <label for="email">Email address</label>
  <input
    type="email"
    id="email"
    aria-describedby="email-hint"
    aria-required="true"
  />
  <p id="email-hint">We'll never share your email.</p>
</form>

<!-- ✅ Good - icon button with label -->
<button aria-label="Close dialog">
  <svg aria-hidden="true"><!-- icon --></svg>
</button>

<!-- ✅ Good - live region for updates -->
<div aria-live="polite" aria-atomic="true">
  {message &&
  <p>{message}</p>
  }
</div>

<!-- ✅ Good - custom combobox -->
<div
  role="combobox"
  aria-expanded="{isOpen}"
  aria-haspopup="listbox"
  aria-controls="options-list"
>
  <input aria-autocomplete="list" aria-activedescendant="{activeOption}" />
  <ul id="options-list" role="listbox">
    {options.map(opt => (
    <li
      key="{opt.id}"
      role="option"
      aria-selected="{opt.id"
      =""
      =""
      ="selected}"
    >
      {opt.label}
    </li>
    ))}
  </ul>
</div>

Images & Media

Rules

  • ✅ DO: Provide alt text for meaningful images
  • ✅ DO: Use empty alt (alt="") for decorative images
  • ✅ DO: Provide captions/transcripts for video/audio
  • ✅ DO: Don't rely on color alone to convey information
  • ❌ DON'T: Use "image of" in alt text
  • ❌ DON'T: Leave alt text empty for informative images

Examples

html
<!-- ✅ Good - meaningful alt text -->
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2 2024" />

<!-- ✅ Good - decorative image -->
<img src="decoration.svg" alt="" />

<!-- ✅ Good - complex image with longer description -->
<figure>
  <img
    src="diagram.png"
    alt="System architecture"
    aria-describedby="diagram-desc"
  />
  <figcaption id="diagram-desc">
    The system consists of three layers: presentation (React), business logic
    (Node.js), and data (PostgreSQL).
  </figcaption>
</figure>

<!-- ✅ Good - video with captions -->
<video controls>
  <source src="video.mp4" type="video/mp4" />
  <track kind="captions" src="captions.vtt" srclang="en" label="English" />
</video>

Forms

Rules

  • ✅ DO: Label all form inputs
  • ✅ DO: Group related fields with <fieldset> and <legend>
  • ✅ DO: Provide clear error messages
  • ✅ DO: Associate errors with inputs
  • ❌ DON'T: Use placeholder as only label
  • ❌ DON'T: Rely on color alone for errors

Examples

html
<!-- ✅ Good - accessible form -->
<form>
  <fieldset>
    <legend>Shipping Address</legend>

    <div>
      <label for="street">Street address</label>
      <input
        type="text"
        id="street"
        aria-invalid={!!errors.street}
        aria-describedby={errors.street ? 'street-error' : undefined}
      />
      {errors.street && (
        <p id="street-error" role="alert" class="error">
          {errors.street}
        </p>
      )}
    </div>

    <div>
      <label for="city">City</label>
      <input type="text" id="city" required />
    </div>
  </fieldset>

  <button type="submit">Submit</button>
</form>

Color & Contrast

Rules

  • ✅ DO: Maintain 4.5:1 contrast ratio for normal text
  • ✅ DO: Maintain 3:1 contrast ratio for large text (18px+)
  • ✅ DO: Don't rely on color alone to convey meaning
  • ✅ DO: Test with color blindness simulators

Examples

css
/* ✅ Good - sufficient contrast */
:root {
  --text-primary: #1a1a1a; /* High contrast on white */
  --text-secondary: #595959; /* Still meets 4.5:1 */
  --background: #ffffff;
}

/* ✅ Good - not just color */
.error {
  color: #d32f2f;
  border-left: 3px solid #d32f2f;
}
.error::before {
  content: "⚠ ";
}

Testing

Tools

  • axe DevTools — Browser extension for automated testing
  • WAVE — Web accessibility evaluation tool
  • Lighthouse — Accessibility audits
  • NVDA/VoiceOver — Screen reader testing

Checklist

  • Navigate with keyboard only
  • Test with screen reader
  • Check color contrast
  • Verify focus management
  • Test at 200% zoom
  • Run automated accessibility audit