Skip to main content
Accessibility 6 mins read Devs2.org

Web Accessibility (a11y) Complete Guide - Build Inclusive Websites

Learn web accessibility from scratch. Discover how to make your websites usable by everyone, including people with disabilities. Master ARIA, semantic HTML, keyboard navigation, and WCAG guidelines.

#Accessibility #a11y #Web Development #WCAG #Inclusive Design #Frontend

When we talk about building great websites, we often focus on stunning visuals, blazing-fast performance, and sleek animations. But there’s a crucial aspect that too many developers overlook: making sure everyone can use your website, regardless of their abilities or the devices they use.

That’s where web accessibility comes in — and no, it’s not just a nice-to-have feature. It’s a fundamental part of professional web development.

web-accessibility-complete-guide

What Is Web Accessibility?

Web accessibility (often abbreviated as a11y — eleven letters between “a” and “y”) means creating websites and web applications that people with disabilities can use effectively.

But here’s the thing: accessibility isn’t just about disability. It’s about inclusive design — building experiences that work for everyone, in every situation.

Who Benefits from Accessibility?

People with disabilities represent approximately 15% of the global population — that’s over 1 billion people. Here are some common types of impairments:

TypeExamples
VisualBlindness, low vision, color blindness, light sensitivity
AuditoryDeafness, hard of hearing, auditory processing disorders
MotorLimited dexterity, tremors, paralysis, missing limbs
CognitiveDyslexia, ADHD, autism, memory impairments

But accessibility also helps:

  • People using mobile devices in bright sunlight
  • Users with slow or unstable internet connections
  • Older users whose abilities change with age
  • Anyone in a distracting environment (like a noisy subway)

The Four Principles of WCAG

The Web Content Accessibility Guidelines (WCAG), published by the World Wide Web Consortium (W3C), define the gold standard for web accessibility. They’re built on four core principles:

1. Perceivable

Information and user interface components must be presentable to users in ways they can perceive.

<!-- ❌ Bad: Image without alternative text -->
<img src="chart.png">

<!-- ✅ Good: Descriptive alt text -->
<img src="chart.png" alt="Sales increased 40% in Q3 compared to Q2">

<!-- ✅ Also good: Decorative images get empty alt -->
<img src="divider.png" alt="">

Key practices:

  • Provide alt text for all meaningful images
  • Use captions and transcripts for videos
  • Ensure content makes sense when styled differently (e.g., dark mode)
  • Don’t rely solely on color to convey information

2. Operable

User interface components and navigation must be operable by all users.

<!-- ❌ Bad: Keyboard trap inside modal -->
<div class="modal" tabindex="-1">
  <button onclick="closeModal()">Close</button>
  <!-- Focus cannot escape this modal -->
</div>

<!-- ✅ Good: Modal with proper focus management -->
<div class="modal" role="dialog" aria-modal="true" tabindex="-1">
  <button onclick="closeModal()" autofocus>Close</button>
  <!-- Tab order is managed; Escape key closes modal -->
</div>

Key practices:

  • Make everything work with keyboard navigation alone
  • Provide enough time for users to read and interact
  • Avoid content that flashes more than three times per second
  • Offer clear navigation mechanisms (skip links, breadcrumbs)

3. Understandable

Information and the operation of the user interface must be understandable.

<!-- ❌ Bad: Unclear button label -->
<button>Submit</button>

<!-- ✅ Good: Clear, descriptive label -->
<button type="submit">Create New Project</button>

<!-- ❌ Bad: Error message doesn't explain the problem -->
<p>Error!</p>

<!-- ✅ Good: Specific error message with guidance -->
<p class="error">Email address is invalid. Please include @ and a domain name (e.g., user@example.com).</p>

Key practices:

  • Use clear, simple language
  • Make error messages specific and actionable
  • Keep layouts consistent across pages
  • Allow users to undo, confirm, or correct mistakes

4. Robust

Content must be robust enough to be interpreted reliably by a wide variety of user agents, including assistive technologies.

<!-- ❌ Bad: Non-standard custom element -->
<my-button onclick="handleClick()">Click Me</my-button>

<!-- ✅ Good: Standard HTML element -->
<button onclick="handleClick()">Click Me</button>

<!-- ❌ Bad: Div used as interactive element -->
<div onclick="toggleMenu()" style="cursor: pointer;">Menu</div>

<!-- ✅ Good: Semantic interactive element -->
<button aria-expanded="false" aria-controls="menu" onclick="toggleMenu()">Menu</button>

Key practices:

  • Use valid, semantic HTML
  • Ensure compatibility with screen readers and other assistive tech
  • Test with multiple browsers and devices
  • Follow web standards consistently

Essential HTML Techniques for Accessibility

The foundation of accessible web development is semantic HTML. When you use the right HTML elements, you get accessibility “for free.”

Proper Heading Structure

Headings create a document outline that screen reader users rely on to navigate content.

<!-- ✅ Correct heading hierarchy -->
<h1>Main Page Title</h1>
  <h2>Section One</h2>
    <h3>Subsection A</h3>
    <h3>Subsection B</h3>
  <h2>Section Two</h2>
    <h3>Subsection C</h3>

<!-- ❌ Wrong: Skipping heading levels -->
<h1>Main Title</h1>
  <h3>Skipped h2! Confusing for screen readers.</h3>

Landmark Regions

Landmarks help screen reader users jump to different sections of a page quickly.

<!-- ✅ Use semantic HTML5 landmark elements -->
<header>
  <nav aria-label="Main navigation">
    <!-- Navigation links -->
  </nav>
</header>

<main>
  <article>
    <!-- Main content -->
  </article>
</main>

<aside>
  <!-- Sidebar content -->
</aside>

<footer>
  <!-- Footer content -->
</footer>

Accessible Forms

Forms are often the most inaccessible part of a website. Here’s how to get it right:

<form action="/subscribe" method="POST">
  <!-- ✅ Each input has a visible label -->
  <div class="form-group">
    <label for="email">Email Address</label>
    <input 
      type="email" 
      id="email" 
      name="email" 
      required 
      autocomplete="email"
      aria-describedby="email-help"
    >
    <small id="email-help">We'll never share your email with anyone.</small>
  </div>

  <!-- ✅ Error messages linked to inputs -->
  <div class="form-group">
    <label for="username">Username</label>
    <input 
      type="text" 
      id="username" 
      name="username" 
      required
      aria-invalid="true"
      aria-describedby="username-error"
    >
    <span id="username-error" class="error" role="alert">
      Username must be at least 3 characters long.
    </span>
  </div>

  <!-- ✅ Radio buttons grouped properly -->
  <fieldset>
    <legend>Preferred Contact Method</legend>
    <label>
      <input type="radio" name="contact" value="email"> Email
    </label>
    <label>
      <input type="radio" name="contact" value="phone"> Phone
    </label>
    <label>
      <input type="radio" name="contact" value="mail"> Postal Mail
    </label>
  </fieldset>

  <button type="submit">Subscribe Now</button>
</form>

Every link should clearly describe its destination or purpose.

<!-- ❌ Bad: Vague link text -->
<a href="/about">Click here</a>
<a href="/blog/post-123">Read more</a>

<!-- ✅ Good: Descriptive link text -->
<a href="/about">Learn more about Devs2.org</a>
<a href="/blog/post-123">Read the complete CSS Grid tutorial</a>

<!-- ✅ For icon-only links, provide accessible name -->
<a href="/search" aria-label="Search our articles">
  <svg><!-- search icon --></svg>
</a>

Color and Visual Design

Color plays a huge role in web design, but relying on color alone excludes users with color blindness or those viewing screens in poor conditions.

Sufficient Color Contrast

The WCAG AA standard requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18pt bold or 14pt regular).

/* ❌ Bad: Low contrast */
.text-low-contrast {
  color: #aaaaaa; /* Light gray on white — fails WCAG */
}

/* ✅ Good: High contrast */
.text-high-contrast {
  color: #333333; /* Dark gray on white — passes WCAG AA */
}

/* ✅ Large text needs less contrast */
.large-text {
  color: #777777; /* Acceptable for large text (18pt+) */
}

Quick reference for minimum contrast ratios:

Text SizeMinimum Ratio (AA)Minimum Ratio (AAA)
Normal (< 18pt)4.5:17:1
Large (≥ 18pt bold / ≥ 14pt regular)3:14.5:1

Don’t Rely on Color Alone

/* ❌ Bad: Only color indicates state */
.input-error {
  border: 2px solid red;
}

/* ✅ Good: Color + icon + text */
.input-error {
  border: 2px solid red;
  background-image: url('error-icon.svg');
  background-repeat: no-repeat;
  background-position: left center;
  padding-left: 2rem;
}

.input-error::after {
  content: "Please fix this field";
  display: block;
  font-size: 0.875rem;
  color: #dc2626;
}

Supporting Dark Mode and Reduced Motion

/* Respect user preferences */
@media (prefers-color-scheme: dark) {
  body {
    background-color: #1a1a1a;
    color: #f0f0f0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

ARIA: When and How to Use It

ARIA (Accessible Rich Internet Applications) fills gaps when native HTML isn’t enough. But remember the First Rule of ARIA:

Don’t use ARIA if a native HTML element will do.

Common ARIA Attributes

<!-- Live regions: Announce dynamic updates to screen readers -->
<div aria-live="polite" aria-atomic="true">
  <!-- This content will be announced when it changes -->
  <span id="notification"></span>
</div>

<script>
// Screen readers will announce this update politely
document.getElementById('notification').textContent = 
  'Your form has been saved successfully.';
</script>

<!-- Expanded/collapsed state -->
<button 
  aria-expanded="false" 
  aria-controls="submenu"
  onclick="toggleMenu()"
>
  Settings
</button>
<div id="submenu" hidden>
  <!-- Menu items -->
</div>

<!-- Loading states -->
<button disabled aria-busy="true">
  Saving...
</button>

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

ARIA Roles You Might Need

RolePurposeExample
role="dialog"Modal dialogPop-up forms, confirmation boxes
role="alert"Urgent messageError notifications
role="tablist"Tab containerTabbed interfaces
role="progressbar"Progress indicatorLoading bars
role="navigation"Navigation sectionNav menus
role="complementary"Side contentSidebars

Keyboard Navigation

Many users navigate entirely with a keyboard. Your site must support this.

Focus Management

/* ✅ Visible focus indicators */
*:focus {
  outline: 3px solid #2563eb;
  outline-offset: 2px;
}

/* ❌ Never remove focus without providing an alternative */
*:focus {
  outline: none; /* BAD — removes keyboard visibility */
}

Help keyboard users bypass repetitive navigation:

<!-- Place this as the very first focusable element -->
<a href="#main-content" class="skip-link">
  Skip to main content
</a>

<nav><!-- Navigation menu --></nav>

<main id="main-content">
  <!-- Page content -->
</main>

<style>
.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  background: #2563eb;
  color: white;
  padding: 0.75rem 1.5rem;
  z-index: 100;
  font-weight: bold;
  text-decoration: none;
}

.skip-link:focus {
  top: 0;
}
</style>

Interactive Elements Must Be Keyboard-Accessible

// ✅ Custom dropdown that works with keyboard
const dropdown = document.querySelector('.custom-dropdown');
const trigger = dropdown.querySelector('.trigger');
const menu = dropdown.querySelector('.menu');

trigger.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowDown' || e.key === 'Enter') {
    e.preventDefault();
    menu.hidden = false;
    trigger.setAttribute('aria-expanded', 'true');
    menu.querySelector('li').focus();
  }
});

// Trap focus within modal dialogs
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    closeModal();
  }
});

Testing Your Accessibility

You can’t make a website accessible without testing it. Here’s a practical testing checklist:

Automated Tools

ToolWhat It DoesBest For
axe DevToolsBrowser extension for real-time analysisDevelopment workflow
LighthouseBuilt into Chrome DevToolsPerformance + accessibility audit
WAVEWeb-based visual feedbackQuick checks on any page
Pa11yCommand-line automated testingCI/CD integration

Manual Testing Checklist

□ Navigate the entire site using ONLY the keyboard (Tab, Shift+Tab, Enter, Space, Escape)
□ Check that focus order follows the visual layout
□ Verify all images have appropriate alt text
□ Confirm heading hierarchy is logical (no skipped levels)
□ Test all forms with labels, error messages, and validation
□ Check color contrast ratios meet WCAG AA standards
□ Verify links have descriptive text
□ Test with a screen reader (NVDA on Windows, VoiceOver on Mac/iOS)
□ Resize browser text up to 200% — does content remain readable?
□ Turn off all styling — is the content still understandable?

Screen Reader Testing

Windows: Download NVDA (free, open-source) macOS/iOS: Enable VoiceOver (Cmd+F5 on Mac, toggle in Settings on iOS) Android: Enable TalkBack (in Accessibility settings)

Pro tip: Try browsing your own website with a screen reader turned on. You’ll quickly discover issues you’d never notice visually.

React

// ✅ Accessible button component
function AccessibleButton({ onClick, children }) {
  return (
    <button 
      onClick={onClick}
      aria-label="Custom action"
      className="btn btn-primary"
    >
      {children}
    </button>
  );
}

// ✅ Accessible modal with focus trapping
function Modal({ isOpen, onClose, title, children }) {
  const modalRef = useRef(null);

  useEffect(() => {
    if (isOpen && modalRef.current) {
      modalRef.current.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div 
      role="dialog" 
      aria-modal="true" 
      aria-labelledby="modal-title"
      onKeyDown={(e) => e.key === 'Escape' && onClose()}
    >
      <h2 id="modal-title">{title}</h2>
      <div ref={modalRef} tabIndex={-1}>
        {children}
      </div>
      <button onClick={onClose} aria-label="Close dialog">✕</button>
    </div>
  );
}

// ✅ Accessible accordion
function AccordionItem({ title, children, isOpen }) {
  return (
    <div className="accordion-item">
      <h3>
        <button
          aria-expanded={isOpen}
          aria-controls={`panel-${title}`}
          onClick={() => {/* toggle logic */}}
        >
          {title}
        </button>
      </h3>
      <div
        id={`panel-${title}`}
        role="region"
        hidden={!isOpen}
      >
        {children}
      </div>
    </div>
  );
}

Astro

---
// ✅ Semantic HTML in Astro templates
---
<article>
  <header>
    <h1>{frontmatter.title}</h1>
    <time datetime={frontmatter.publishedDate}>
      {new Date(frontmatter.publishedDate).toLocaleDateString('vi-VN')}
    </time>
  </header>
  
  <main>
    <slot />
  </main>

  <footer>
    <p>Written by {frontmatter.author}</p>
  </footer>
</article>

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

Common Accessibility Mistakes (And How to Fix Them)

Mistake 1: Using <div> and <span> for Everything

<!-- ❌ Bad -->
<div onclick="navigate('/home')">Home</div>
<span class="link" onclick="openModal()">More Info</span>

<!-- ✅ Good -->
<a href="/home">Home</a>
<button onclick="openModal()">More Info</button>

Mistake 2: Auto-playing Media

<!-- ❌ Bad: Audio auto-plays -->
<audio autoplay>
  <source src="intro.mp3" type="audio/mpeg">
</audio>

<!-- ✅ Good: User controls playback -->
<audio controls preload="metadata">
  <source src="intro.mp3" type="audio/mpeg">
</audio>

<!-- ✅ Even better: Muted by default -->
<video muted playsinline controls>
  <source src="demo.mp4" type="video/mp4">
</video>

Mistake 3: Missing Form Labels

<!-- ❌ Bad: No visible label -->
<input type="text" placeholder="Enter your name">

<!-- ✅ Good: Visible label -->
<label for="name">Full Name</label>
<input type="text" id="name" placeholder="Enter your name">

<!-- ✅ Alternative: Visually hidden label -->
<label for="search" class="sr-only">Search</label>
<input type="search" id="search" placeholder="Search...">

<style>
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
</style>

Mistake 4: Ignoring Focus States

/* ❌ Bad: Invisible focus */
button:focus {
  outline: none;
}

/* ✅ Good: Clear, visible focus */
button:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.2);
}

The Business Case for Accessibility

Beyond ethics and law, accessibility makes business sense:

  • Market reach: 1 billion+ potential users with disabilities worldwide
  • SEO boost: Accessible sites tend to rank higher (search engines love semantic HTML)
  • Legal compliance: Laws like Section 508, ADA, and EN 301 549 require accessibility
  • Better UX: Accessible features benefit all users (e.g., captions help in noisy environments)
  • Reduced maintenance: Clean, semantic code is easier to maintain and extend

Getting Started: Your Accessibility Action Plan

Ready to make your website more accessible? Here’s a step-by-step plan:

Phase 1: Quick Wins (1-2 days)

  1. Add alt text to all images
  2. Ensure all links have descriptive text
  3. Check heading hierarchy — no skipped levels
  4. Add labels to all form inputs
  5. Verify color contrast meets WCAG AA

Phase 2: Core Improvements (1-2 weeks)

  1. Implement keyboard navigation throughout
  2. Add skip navigation links
  3. Ensure focus indicators are visible
  4. Test with a screen reader
  5. Add ARIA attributes where needed

Phase 3: Advanced (Ongoing)

  1. Conduct user testing with people who have disabilities
  2. Integrate automated accessibility testing into CI/CD
  3. Create an accessibility statement for your site
  4. Train your team on accessible development practices
  5. Regularly audit and remediate new issues

Conclusion

Web accessibility isn’t a checkbox to tick or a project to finish. It’s a mindset — a commitment to building technology that works for everyone.

The good news? You don’t need to be perfect to make a difference. Start with the basics: use semantic HTML, add alt text, ensure keyboard navigation, and check your color contrast. Every small improvement makes the web a little more inclusive.

Remember: the best accessibility feature is a well-designed, thoughtfully built website. When you prioritize accessibility from the start — not as an afterthought — you create better products for everyone.

As the saying goes in the accessibility community:

“Nothing about us without us.”

Involve people with disabilities in your design and testing process, listen to their feedback, and keep learning. The web is better when everyone can participate.


Resources for Further Learning:

Recently Used Tools