Back to Blog
Accessibility

Building Accessible Websites: A Developer's Guide to Inclusive Design

Essential tips and techniques for creating websites that are accessible to users with disabilities, ensuring your site works for everyone.

David Wilson
January 5, 2024
9 min read
Building Accessible Websites: A Developer's Guide to Inclusive Design

Building Accessible Websites: A Developer's Guide to Inclusive Design

Web accessibility isn't just a nice-to-have feature—it's a fundamental requirement for creating inclusive digital experiences. With over 1 billion people worldwide living with disabilities, building accessible websites ensures your content reaches the widest possible audience while often improving the experience for all users.

Understanding Web Accessibility

Web accessibility means designing and developing websites that can be used by people with various disabilities, including:

  • Visual impairments (blindness, low vision, color blindness)
  • Hearing impairments (deafness, hard of hearing)
  • Motor impairments (limited fine motor control, paralysis)
  • Cognitive impairments (dyslexia, ADHD, autism)

The Business Case for Accessibility

Beyond moral obligations, accessibility makes business sense:

  • Legal compliance with ADA and other regulations
  • Expanded market reach to 15% of the global population
  • Improved SEO through better semantic markup
  • Enhanced user experience for all users
  • Better code quality and maintainability

WCAG Guidelines Overview

The Web Content Accessibility Guidelines (WCAG) 2.1 provide the foundation for web accessibility, organized around four principles:

1. Perceivable

Information must be presentable in ways users can perceive.

2. Operable

Interface components must be operable by all users.

3. Understandable

Information and UI operation must be understandable.

4. Robust

Content must be robust enough for various assistive technologies.

Essential Accessibility Techniques

Semantic HTML Foundation

Use proper HTML elements for their intended purpose:

<!-- Good: Semantic structure -->
<header>
  <nav>
    <ul>
      <li><a href="/home">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h1>Article Title</h1>
    <p>Article content...</p>
  </article>
</main>

<footer>
  <p>&copy; 2024 Company Name</p>
</footer>
<!-- Avoid: Non-semantic markup -->
<div class="header">
  <div class="nav">
    <div class="nav-item">Home</div>
    <div class="nav-item">About</div>
  </div>
</div>

Proper Heading Structure

Create logical heading hierarchies:

<h1>Main Page Title</h1>
  <h2>Section Title</h2>
    <h3>Subsection Title</h3>
    <h3>Another Subsection</h3>
  <h2>Another Section</h2>
    <h3>Subsection Title</h3>

Best Practices:

  • Use only one H1 per page
  • Don't skip heading levels
  • Use headings for structure, not styling
  • Ensure headings accurately describe content

Alternative Text for Images

Provide meaningful alt text for images:

<!-- Informative image -->
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2 2024">

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

<!-- Complex image -->
<img src="complex-chart.png" alt="Quarterly sales data" 
     longdesc="sales-data-description.html">

Alt Text Guidelines:

  • Describe the image's purpose, not appearance
  • Keep it concise (under 125 characters)
  • Use empty alt="" for decorative images
  • Provide longer descriptions for complex images

Keyboard Navigation

Ensure all interactive elements are keyboard accessible:

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

/* Skip to main content link */
.skip-link {
  position: absolute;
  top: -40px;
  left: 6px;
  background: #000;
  color: #fff;
  padding: 8px;
  text-decoration: none;
  z-index: 1000;
}

.skip-link:focus {
  top: 6px;
}

Keyboard Navigation Requirements:

  • All interactive elements must be focusable
  • Logical tab order throughout the page
  • Visible focus indicators
  • Skip links for navigation
  • Keyboard shortcuts for complex interfaces

Color and Contrast

Ensure sufficient color contrast and don't rely solely on color:

/* Good contrast ratios */
.text-normal {
  color: #333333; /* 12.6:1 contrast ratio on white */
}

.text-large {
  color: #666666; /* 5.7:1 contrast ratio - acceptable for large text */
}

/* Don't rely only on color */
.error {
  color: #d32f2f;
  border-left: 4px solid #d32f2f;
}

.error::before {
  content: "⚠ ";
  font-weight: bold;
}

Contrast Requirements:

  • Normal text: 4.5:1 minimum ratio
  • Large text (18pt+ or 14pt+ bold): 3:1 minimum
  • Non-text elements: 3:1 minimum
  • Use tools like WebAIM's contrast checker

Form Accessibility

Proper Form Labels

Associate labels with form controls:

<!-- Explicit labeling -->
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required>

<!-- Implicit labeling -->
<label>
  Phone Number
  <input type="tel" name="phone">
</label>

<!-- Using aria-label -->
<input type="search" aria-label="Search products" placeholder="Search...">

Error Handling and Validation

Provide clear, accessible error messages:

<div class="form-group">
  <label for="password">Password</label>
  <input type="password" id="password" name="password" 
         aria-describedby="password-help password-error"
         aria-invalid="true" required>
  
  <div id="password-help">
    Password must be at least 8 characters long
  </div>
  
  <div id="password-error" class="error" role="alert">
    Password is required and must be at least 8 characters
  </div>
</div>

Fieldset and Legend for Groups

Group related form controls:

<fieldset>
  <legend>Shipping Address</legend>
  
  <label for="street">Street Address</label>
  <input type="text" id="street" name="street">
  
  <label for="city">City</label>
  <input type="text" id="city" name="city">
  
  <label for="zip">ZIP Code</label>
  <input type="text" id="zip" name="zip">
</fieldset>

ARIA (Accessible Rich Internet Applications)

ARIA Roles

Define the purpose of elements:

<!-- Navigation landmark -->
<nav role="navigation" aria-label="Main navigation">
  <ul>...</ul>
</nav>

<!-- Button role for non-button elements -->
<div role="button" tabindex="0" onclick="toggleMenu()">
  Menu
</div>

<!-- Alert for important messages -->
<div role="alert" id="status-message">
  Form submitted successfully!
</div>

ARIA Properties

Provide additional information about elements:

<!-- Expandable content -->
<button aria-expanded="false" aria-controls="menu">
  Menu
</button>
<ul id="menu" hidden>
  <li><a href="/home">Home</a></li>
  <li><a href="/about">About</a></li>
</ul>

<!-- Progress indicator -->
<div role="progressbar" aria-valuenow="32" aria-valuemin="0" 
     aria-valuemax="100" aria-label="Upload progress">
  32% complete
</div>

ARIA States

Communicate dynamic changes:

<!-- Toggle button -->
<button aria-pressed="false" onclick="toggleFeature()">
  Enable Feature
</button>

<!-- Loading state -->
<button aria-busy="true" disabled>
  <span aria-hidden="true">⏳</span>
  Loading...
</button>

Dynamic Content and Single Page Applications

Live Regions

Announce dynamic content changes:

<!-- Polite announcements -->
<div aria-live="polite" id="status">
  <!-- Status updates appear here -->
</div>

<!-- Assertive announcements -->
<div aria-live="assertive" id="errors">
  <!-- Error messages appear here -->
</div>

<!-- Atomic updates -->
<div aria-live="polite" aria-atomic="true" id="timer">
  Time remaining: 5:00
</div>

Focus Management

Manage focus for dynamic interfaces:

// Focus management for modal dialogs
function openModal(modalId) {
  const modal = document.getElementById(modalId);
  const firstFocusable = modal.querySelector('button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
  
  modal.style.display = 'block';
  modal.setAttribute('aria-hidden', 'false');
  
  if (firstFocusable) {
    firstFocusable.focus();
  }
}

// Focus management for single page apps
function navigateToPage(pageId) {
  const page = document.getElementById(pageId);
  const heading = page.querySelector('h1');
  
  // Update page visibility
  document.querySelectorAll('.page').forEach(p => p.hidden = true);
  page.hidden = false;
  
  // Focus the main heading
  if (heading) {
    heading.setAttribute('tabindex', '-1');
    heading.focus();
  }
}

Testing for Accessibility

Automated Testing Tools

Use tools to catch common issues:

Browser Extensions:

  • axe DevTools for comprehensive testing
  • WAVE for visual feedback
  • Lighthouse accessibility audit

Command Line Tools:

  • axe-core for CI/CD integration
  • Pa11y for automated testing
  • Accessibility Insights for Windows

Manual Testing Techniques

Keyboard Testing:

  1. Navigate using only Tab, Shift+Tab, Enter, Space, and arrow keys
  2. Ensure all interactive elements are reachable
  3. Verify logical tab order
  4. Test skip links and keyboard shortcuts

Screen Reader Testing:

  1. Test with NVDA (Windows), VoiceOver (Mac), or JAWS
  2. Navigate by headings, landmarks, and links
  3. Verify form labels and error messages
  4. Test dynamic content announcements

Visual Testing:

  1. Check color contrast ratios
  2. Test with high contrast mode
  3. Verify content at 200% zoom
  4. Test with different font sizes

User Testing with Disabilities

Include users with disabilities in your testing process:

  • Recruit participants with various disabilities
  • Observe real usage patterns
  • Gather feedback on pain points
  • Iterate based on user insights

Common Accessibility Mistakes

1. Poor Color Contrast

Problem: Text that's difficult to read Solution: Use contrast checking tools and test with various vision conditions

2. Missing Alt Text

Problem: Images without alternative text Solution: Provide meaningful alt text for all informative images

3. Keyboard Traps

Problem: Users can't navigate away from elements Solution: Ensure all focusable elements can be exited with keyboard

4. Missing Form Labels

Problem: Form controls without proper labels Solution: Associate all form controls with descriptive labels

5. Inaccessible Custom Components

Problem: Custom widgets that don't work with assistive technology Solution: Use ARIA attributes and follow established patterns

Accessibility in Modern Frameworks

React Accessibility

// Accessible React component
function AccessibleButton({ children, onClick, disabled = false }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      aria-disabled={disabled}
      className="btn"
    >
      {children}
    </button>
  );
}

// Focus management in React
function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef();
  
  useEffect(() => {
    if (isOpen) {
      modalRef.current?.focus();
    }
  }, [isOpen]);
  
  return isOpen ? (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      tabIndex={-1}
      className="modal"
    >
      {children}
    </div>
  ) : null;
}

Vue.js Accessibility

<template>
  <div>
    <!-- Accessible form in Vue -->
    <label :for="inputId">{{ label }}</label>
    <input
      :id="inputId"
      v-model="value"
      :aria-describedby="hasError ? errorId : helpId"
      :aria-invalid="hasError"
      @input="$emit('input', $event.target.value)"
    >
    
    <div v-if="helpText" :id="helpId">
      {{ helpText }}
    </div>
    
    <div v-if="hasError" :id="errorId" role="alert">
      {{ errorMessage }}
    </div>
  </div>
</template>

<script>
export default {
  props: ['label', 'value', 'helpText', 'errorMessage'],
  computed: {
    inputId() {
      return `input-${this._uid}`;
    },
    helpId() {
      return `help-${this._uid}`;
    },
    errorId() {
      return `error-${this._uid}`;
    },
    hasError() {
      return !!this.errorMessage;
    }
  }
};
</script>

Creating an Accessibility Checklist

Development Checklist

HTML Structure:

  • Proper semantic HTML elements used
  • Logical heading hierarchy (H1-H6)
  • Valid HTML markup
  • Page has a unique, descriptive title

Images and Media:

  • All images have appropriate alt text
  • Decorative images have empty alt attributes
  • Complex images have long descriptions
  • Videos have captions and transcripts

Forms:

  • All form controls have labels
  • Error messages are clear and associated
  • Required fields are indicated
  • Form validation is accessible

Navigation:

  • Keyboard navigation works throughout
  • Focus indicators are visible
  • Skip links are provided
  • Tab order is logical

Color and Contrast:

  • Sufficient color contrast ratios
  • Information not conveyed by color alone
  • Content readable at 200% zoom
  • High contrast mode supported

Testing Checklist

Automated Testing:

  • Run axe-core or similar tool
  • Check Lighthouse accessibility score
  • Validate HTML markup
  • Test with accessibility browser extensions

Manual Testing:

  • Navigate with keyboard only
  • Test with screen reader
  • Verify at different zoom levels
  • Check in high contrast mode

User Testing:

  • Test with users with disabilities
  • Gather feedback on usability
  • Iterate based on user insights
  • Document accessibility features

Conclusion

Building accessible websites is an ongoing process that requires commitment, knowledge, and continuous testing. By following these guidelines and making accessibility a priority from the start of your projects, you'll create better experiences for all users while meeting legal requirements and expanding your potential audience.

Remember that accessibility is not a one-time checklist but an ongoing commitment to inclusive design. Stay updated with WCAG guidelines, test regularly with real users, and always consider the diverse needs of your audience.

The investment in accessibility pays dividends not just in reaching more users, but in creating higher-quality, more maintainable code that benefits everyone.


Need help making your website accessible? Contact our accessibility experts for an audit and remediation plan tailored to your specific needs.

Tags:
accessibility
a11y
inclusive design
wcag
development
Share this article:

Stay Updated with Our Latest Insights

Get the latest articles, tips, and industry insights delivered straight to your inbox