Files
awesome-copilot/skills/web-coder/references/accessibility.md
John Haugabook 8fedf95507 new skill web-coder (#881)
* new skill web-coder

* codespellrc: add aNULL HTTPS config cipher string

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

* Apply suggestion from @jhauga

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 21:43:04 +11:00

6.9 KiB

Accessibility Reference

Web accessibility ensures content is usable by everyone, including people with disabilities.

WCAG (Web Content Accessibility Guidelines)

Levels

  • A: Minimum level
  • AA: Standard target (legal requirement in many jurisdictions)
  • AAA: Enhanced accessibility

Four Principles (POUR)

  1. Perceivable: Information presented in ways users can perceive
  2. Operable: UI components and navigation are operable
  3. Understandable: Information and UI operation is understandable
  4. Robust: Content works with current and future technologies

ARIA (Accessible Rich Internet Applications)

ARIA Roles

<!-- Landmark roles -->
<nav role="navigation">
<main role="main">
<aside role="complementary">
<footer role="contentinfo">

<!-- Widget roles -->
<div role="button" tabindex="0">Click me</div>
<div role="tab" aria-selected="true">Tab 1</div>
<div role="dialog" aria-labelledby="dialogTitle">

<!-- Document structure -->
<div role="list">
  <div role="listitem">Item 1</div>
</div>

ARIA Attributes

<!-- States -->
<button aria-pressed="true">Toggle</button>
<input aria-invalid="true" aria-errormessage="error1">
<div aria-expanded="false" aria-controls="menu">Menu</div>

<!-- Properties -->
<img alt="" aria-hidden="true">
<input aria-label="Search" type="search">
<dialog aria-labelledby="title" aria-describedby="desc">
  <h2 id="title">Dialog Title</h2>
  <p id="desc">Description</p>
</dialog>

<!-- Relationships -->
<label id="label1" for="input1">Name:</label>
<input id="input1" aria-labelledby="label1">

<!-- Live regions -->
<div aria-live="polite" aria-atomic="true">
  Status updated
</div>

Keyboard Navigation

Tab Order

<!-- Natural tab order -->
<button>First</button>
<button>Second</button>

<!-- Custom tab order (avoid if possible) -->
<button tabindex="1">First</button>
<button tabindex="2">Second</button>

<!-- Programmatically focusable  (not in tab order) -->
<div tabindex="-1">Not in tab order</div>

<!-- In tab order -->
<div tabindex="0" role="button">Custom button</div>

Keyboard Events

element.addEventListener('keydown', (e) => {
  switch(e.key) {
    case 'Enter':
    case ' ': // Space
      // Activate
      break;
    case 'Escape':
      // Close/cancel
      break;
    case 'ArrowUp':
    case 'ArrowDown':
    case 'ArrowLeft':
    case 'ArrowRight':
      // Navigate
      break;
  }
});

Semantic HTML

<!-- ✅ Good: semantic elements -->
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/">Home</a></li>
  </ul>
</nav>

<!-- ❌ Bad: non-semantic -->
<div class="nav">
  <div><a href="/">Home</a></div>
</div>

<!-- ✅ Good: proper headings hierarchy -->
<h1>Page Title</h1>
  <h2>Section</h2>
    <h3>Subsection</h3>

<!-- ❌ Bad: skipping levels -->
<h1>Page Title</h1>
  <h3>Skipped h2</h3>

Forms Accessibility

<form>
  <!-- Labels -->
  <label for="name">Name:</label>
  <input type="text" id="name" name="name" required aria-required="true">
  
  <!-- Error messages -->
  <input
    type="email"
    id="email"
    aria-invalid="true"
    aria-describedby="email-error">
  <span id="email-error" role="alert">
    Please enter a valid email
  </span>
  
  <!-- Fieldset for groups -->
  <fieldset>
    <legend>Choose an option</legend>
    <label>
      <input type="radio" name="option" value="a">
      Option A
    </label>
    <label>
      <input type="radio" name="option" value="b">
      Option B
    </label>
  </fieldset>
  
  <!-- Help text -->
  <label for="password">Password:</label>
  <input
    type="password"
    id="password"
    aria-describedby="password-help">
  <span id="password-help">
    Must be at least 8 characters
  </span>
</form>

Images and Media

<!-- Informative image -->
<img src="chart.png" alt="Sales increased 50% in Q1">

<!-- Decorative image -->
<img src="decorative.png" alt="" role="presentation">

<!-- Complex image -->
<figure>
  <img src="data-viz.png" alt="Sales data visualization">
  <figcaption>
    Detailed description of the data...
  </figcaption>
</figure>

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

Color and Contrast

WCAG Requirements

  • Level AA: 4.5:1 for normal text, 3:1 for large text
  • Level AAA: 7:1 for normal text, 4.5:1 for large text
/* ✅ Good contrast */
.text {
  color: #000; /* Black */
  background: #fff; /* White */
  /* Contrast: 21:1 */
}

/* Don't rely on color alone */
.error {
  color: red;
  /* ✅ Also use icon or text */
  &::before {
    content: '⚠ ';
  }
}

Screen Readers

Best Practices

<!-- Skip links for navigation -->
<a href="#main-content" class="skip-link">
  Skip to main content
</a>

<!-- Accessible headings -->
<h1>Main heading (only one)</h1>

<!-- Descriptive links -->
<!-- ❌ Bad -->
<a href="/article">Read more</a>

<!-- ✅ Good -->
<a href="/article">Read more about accessibility</a>

<!-- Hidden content (screen reader only) -->
<span class="sr-only">
  Additional context for screen readers
</span>
/* Screen reader only class */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

Focus Management

/* Visible focus indicator */
:focus {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* Don't remove focus entirely */
/* ❌ Bad */
:focus {
  outline: none;
}

/* ✅ Good: custom focus style */
:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
// Focus management in modal
function openModal() {
  modal.showModal();
  modal.querySelector('button').focus();
  
  // Trap focus
  modal.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      trapFocus(e, modal);
    }
  });
}

Testing Tools

  • axe DevTools: Browser extension
  • WAVE: Web accessibility evaluation tool
  • NVDA: Screen reader (Windows)
  • JAWS: Screen reader (Windows)
  • VoiceOver: Screen reader (macOS/iOS)
  • Lighthouse: Automated audits

Checklist

  • Semantic HTML used
  • All images have alt text
  • Color contrast meets WCAG AA
  • Keyboard navigation works
  • Focus indicators visible
  • Forms have labels
  • Heading hierarchy correct
  • ARIA used appropriately
  • Screen reader tested
  • No keyboard traps

Glossary Terms

Key Terms Covered:

  • Accessibility
  • Accessibility tree
  • Accessible description
  • Accessible name
  • ARIA
  • ATAG
  • Boolean attribute (ARIA)
  • Screen reader
  • UAAG
  • WAI
  • WCAG

Additional Resources