Skip to main content
← ブログに戻る

Introduction to Web Accessibility

6 min read著者: pycabbage
GuideAccessibility

Introduction to Web Accessibility

Web accessibility ensures that websites and applications are usable by everyone, including people with disabilities. It's not just about compliance—it's about creating inclusive experiences that benefit all users.

Why Accessibility Matters

Accessibility impacts everyone:

  • 1 in 4 adults have some form of disability
  • Legal requirements: ADA, Section 508, European Accessibility Act
  • Better UX for all: Accessible sites are easier to use
  • SEO benefits: Accessibility improvements often boost SEO
  • Larger audience: Don't exclude potential users

Understanding WCAG Guidelines

The Web Content Accessibility Guidelines (WCAG) provide the foundation for accessibility:

Four Main Principles (POUR)

  1. Perceivable: Information must be presentable in different ways
  2. Operable: Interface components must be operable
  3. Understandable: Information and UI operation must be understandable
  4. Robust: Content must work with various assistive technologies

Conformance Levels

  • Level A: Basic accessibility features
  • Level AA: Removes major barriers (recommended target)
  • Level AAA: Highest level (often impractical for entire sites)

Semantic HTML

The foundation of accessibility is proper HTML:

Use Semantic Elements

<!-- Bad -->
<div class="header">
  <div class="nav">
    <span onclick="navigate()">Home</span>
  </div>
</div>

<!-- Good -->
<header>
  <nav>
    <a href="/">Home</a>
  </nav>
</header>

Heading Hierarchy

<main>
  <h1>Page Title</h1>
  
  <section>
    <h2>Section Title</h2>
    <h3>Subsection</h3>
    <h3>Another Subsection</h3>
  </section>
  
  <section>
    <h2>Another Section</h2>
  </section>
</main>

Keyboard Navigation

Ensure everything is keyboard accessible:

Focus Management

/* Visible focus indicators */
:focus {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

/* Skip to main content link */
.skip-link {
  position: absolute;
  left: -9999px;
}

.skip-link:focus {
  position: absolute;
  left: 6px;
  top: 6px;
  z-index: 999;
}

Tab Order

<!-- Use tabindex sparingly -->
<button>First (natural order)</button>
<button>Second (natural order)</button>
<button tabindex="-1">Not tabbable</button>
<button tabindex="0">Tabbable with natural order</button>

Keyboard Traps

// Trap focus in modal
function trapFocus(element) {
  const focusableElements = element.querySelectorAll(
    'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
  );
  const firstFocusable = focusableElements[0];
  const lastFocusable = focusableElements[focusableElements.length - 1];

  element.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstFocusable) {
        e.preventDefault();
        lastFocusable.focus();
      } else if (!e.shiftKey && document.activeElement === lastFocusable) {
        e.preventDefault();
        firstFocusable.focus();
      }
    }
  });
}

ARIA (Accessible Rich Internet Applications)

ARIA helps make complex UI patterns accessible:

ARIA Rules

  1. Don't use ARIA if you can use semantic HTML
  2. Don't change native semantics unnecessarily
  3. All interactive elements must be keyboard accessible
  4. Don't hide focusable elements
  5. Provide accessible names for all elements

Common ARIA Patterns

<!-- Accessible button -->
<button 
  aria-label="Close dialog"
  aria-pressed="false"
  onclick="toggleDialog()"
>
  <svg><!-- icon --></svg>
</button>

<!-- Live regions for dynamic content -->
<div aria-live="polite" aria-atomic="true">
  <p>Form saved successfully!</p>
</div>

<!-- Describing relationships -->
<input 
  id="email" 
  type="email" 
  aria-describedby="email-error"
  required
/>
<span id="email-error" role="alert">
  Please enter a valid email
</span>

Forms and Inputs

Accessible forms are crucial for user interaction:

Label Association

<!-- Explicit labels -->
<label for="username">Username</label>
<input id="username" type="text" required />

<!-- Implicit labels -->
<label>
  Password
  <input type="password" required />
</label>

<!-- Multiple descriptions -->
<label for="phone">Phone Number</label>
<input 
  id="phone" 
  type="tel"
  aria-describedby="phone-format phone-error"
/>
<span id="phone-format">Format: (123) 456-7890</span>
<span id="phone-error" role="alert">Invalid phone number</span>

Error Handling

function AccessibleForm() {
  const [errors, setErrors] = useState({});

  return (
    <form aria-label="Contact form">
      <div role="group">
        <label htmlFor="email">
          Email <span aria-label="required">*</span>
        </label>
        <input
          id="email"
          type="email"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? "email-error" : undefined}
        />
        {errors.email && (
          <span id="email-error" role="alert">
            {errors.email}
          </span>
        )}
      </div>
    </form>
  );
}

Images and Media

Make visual content accessible:

Alternative Text

<!-- Informative images -->
<img 
  src="chart.png" 
  alt="Sales increased 25% from 2023 to 2024"
/>

<!-- Decorative images -->
<img src="decoration.png" alt="" role="presentation" />

<!-- Complex images -->
<figure>
  <img 
    src="complex-diagram.png" 
    alt="System architecture diagram"
    aria-describedby="diagram-desc"
  />
  <figcaption id="diagram-desc">
    The system consists of three layers: presentation, 
    business logic, and data storage...
  </figcaption>
</figure>

Video Accessibility

<video controls>
  <source src="video.mp4" type="video/mp4" />
  <track 
    kind="captions" 
    src="captions.vtt" 
    srclang="en" 
    label="English"
  />
  <track 
    kind="descriptions" 
    src="descriptions.vtt" 
    srclang="en"
    label="English Audio Descriptions"
  />
</video>

Color and Contrast

Ensure sufficient color contrast:

Contrast Requirements

  • Normal text: 4.5:1 ratio
  • Large text (18pt+): 3:1 ratio
  • UI components: 3:1 ratio
/* Good contrast examples */
.text-primary {
  color: #1a1a1a; /* on white: 21:1 */
  background: #ffffff;
}

.text-secondary {
  color: #666666; /* on white: 5.74:1 */
  background: #ffffff;
}

/* Don't rely on color alone */
.error {
  color: #d32f2f;
  font-weight: bold;
}

.error::before {
  content: "⚠ "; /* Icon for non-color indicator */
}

Testing for Accessibility

Automated Testing

// React with react-axe
import axe from '@axe-core/react';

if (process.env.NODE_ENV === 'development') {
  axe(React, ReactDOM, 1000);
}

Manual Testing Checklist

  • [ ] Keyboard only: Navigate without mouse
  • [ ] Screen reader: Test with NVDA/JAWS/VoiceOver
  • [ ] Color contrast: Check with tools
  • [ ] Zoom to 200%: Content should reflow
  • [ ] Disable CSS: Content should make sense
  • [ ] Check headings: Logical hierarchy

Tools

  1. axe DevTools: Browser extension
  2. WAVE: Web accessibility evaluation
  3. Lighthouse: Built into Chrome
  4. Screen readers: NVDA (free), JAWS, VoiceOver
  5. Keyboard navigation tester: Browser built-in

Common Accessibility Patterns

Accessible Modal

function AccessibleModal({ isOpen, onClose, title, children }) {
  useEffect(() => {
    if (isOpen) {
      // Save last focused element
      const lastFocused = document.activeElement;
      
      // Focus modal
      modalRef.current?.focus();
      
      return () => {
        // Restore focus on close
        lastFocused?.focus();
      };
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      ref={modalRef}
      tabIndex={-1}
    >
      <h2 id="modal-title">{title}</h2>
      <button
        onClick={onClose}
        aria-label="Close modal"
      >
        ×
      </button>
      {children}
    </div>
  );
}

Accessible Navigation

<nav aria-label="Main navigation">
  <ul>
    <li>
      <a href="/" aria-current="page">Home</a>
    </li>
    <li>
      <a href="/about">About</a>
    </li>
    <li>
      <button
        aria-expanded="false"
        aria-controls="products-menu"
      >
        Products
      </button>
      <ul id="products-menu" hidden>
        <li><a href="/software">Software</a></li>
        <li><a href="/hardware">Hardware</a></li>
      </ul>
    </li>
  </ul>
</nav>

Conclusion

Web accessibility is not optional—it's essential for creating inclusive web experiences. Start with semantic HTML, ensure keyboard accessibility, provide appropriate ARIA labels, and test with real assistive technologies.

Remember: accessibility is not a feature to add later, but a fundamental aspect of good web development. By following these guidelines, you'll create better experiences for all users, not just those with disabilities.

Make the web accessible to everyone—it's the right thing to do, and it makes business sense too.