ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

Accessibility Engineering: Building Interfaces Everyone Can Use

Implement web accessibility (a11y) as an engineering practice, not an afterthought. Covers WCAG compliance, semantic HTML, keyboard navigation, screen reader testing, ARIA patterns, color contrast, and the automated testing that catches 30% of issues before manual review catches the rest.

Accessibility is not a feature — it is a quality attribute, like performance or security. You do not ship a “performance version” of your app. You ship an app that is fast. The same applies to accessibility: you ship an app that everyone can use, including the 15% of the global population living with some form of disability.

Beyond the moral imperative, accessibility is a legal requirement in many jurisdictions (ADA, EAA, Section 508) and a business advantage — accessible interfaces are better for everyone, not just users with disabilities.


The Four Principles (POUR)

PrincipleWhat It MeansExample
PerceivableUsers can perceive the contentAlt text on images, captions on video, sufficient color contrast
OperableUsers can interact with the interfaceKeyboard navigation, no time limits, no seizure-inducing animations
UnderstandableUsers can understand the contentClear language, consistent navigation, error prevention
RobustContent works with assistive technologiesSemantic HTML, valid ARIA, tested with screen readers

WCAG Compliance Levels

LevelRequirementTarget
AMinimum accessibilityLegal minimum in most jurisdictions
AAStandard accessibilityTarget this. Most regulations require AA.
AAAEnhanced accessibilityAspirational. Not required but beneficial.

Key WCAG AA Requirements

RequirementCriterionWhat to Do
Color contrast (text)4.5:1 for normal text, 3:1 for large textUse contrast checker tools
Color contrast (UI)3:1 for interactive elementsButtons, form fields, focus indicators
Keyboard navigationAll functionality via keyboardTab order, focus management, no keyboard traps
Alt textAll images have descriptive alternativesMeaningful alt text, empty alt for decorative
Form labelsEvery input has an associated label<label for="email"> or aria-label
Error identificationErrors are described in textNot just red border — text explanation
ResizeContent usable at 200% zoomResponsive design, no horizontal scroll
Focus visibleKeyboard focus is always visibleCustom focus styles, never outline: none

Semantic HTML: The Foundation

<!-- ❌ Div soup: no semantic meaning, invisible to screen readers -->
<div class="header">
  <div class="nav">
    <div class="nav-item" onclick="navigate('/home')">Home</div>
    <div class="nav-item" onclick="navigate('/about')">About</div>
  </div>
</div>
<div class="main">
  <div class="article">
    <div class="title">How to Build Accessible Forms</div>
    <div class="content">...</div>
  </div>
</div>

<!-- ✅ Semantic HTML: meaningful structure, works with assistive tech -->
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/home">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>
<main>
  <article>
    <h1>How to Build Accessible Forms</h1>
    <p>...</p>
  </article>
</main>

Landmark Roles

ElementRolePurpose
<header>bannerSite-wide header
<nav>navigationNavigation links
<main>mainPrimary content
<aside>complementaryRelated content
<footer>contentinfoSite-wide footer
<form>formUser input
<section>region (with aria-label)Thematic grouping

Keyboard Navigation

Essential keyboard interactions:

  Tab        → Move to next focusable element
  Shift+Tab  → Move to previous focusable element
  Enter      → Activate links, buttons, submit forms
  Space      → Activate buttons, toggle checkboxes
  Arrow keys → Navigate within components (tabs, menus, radio groups)
  Escape     → Close modals, menus, popups

Focus Management

// ✅ Managing focus when opening a modal
function openModal(modalElement) {
  const previousFocus = document.activeElement;

  modalElement.hidden = false;
  modalElement.setAttribute('aria-modal', 'true');

  // Move focus to first focusable element in modal
  const firstFocusable = modalElement.querySelector(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  firstFocusable?.focus();

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

function closeModal(modalElement, previousFocus) {
  modalElement.hidden = true;
  modalElement.removeAttribute('aria-modal');
  previousFocus?.focus(); // Return focus to trigger element
}

ARIA: When HTML Is Not Enough

First rule of ARIA: Do not use ARIA if native HTML can do the job.

<!-- ❌ ARIA that duplicates native behavior -->
<div role="button" tabindex="0" aria-label="Submit" onclick="submit()">
  Submit
</div>

<!-- ✅ Native HTML — inherently accessible -->
<button type="submit">Submit</button>

When ARIA Is Necessary

<!-- Custom tab component — no native HTML equivalent -->
<div role="tablist" aria-label="Account settings">
  <button role="tab" aria-selected="true" aria-controls="panel-profile"
          id="tab-profile">
    Profile
  </button>
  <button role="tab" aria-selected="false" aria-controls="panel-security"
          id="tab-security" tabindex="-1">
    Security
  </button>
</div>
<div role="tabpanel" id="panel-profile" aria-labelledby="tab-profile">
  Profile settings content...
</div>
<div role="tabpanel" id="panel-security" aria-labelledby="tab-security"
     hidden>
  Security settings content...
</div>

<!-- Live regions for dynamic content -->
<div aria-live="polite" aria-atomic="true">
  <!-- Screen reader announces when content changes -->
  <p>3 items in your cart</p>
</div>

<!-- Loading states -->
<div aria-busy="true" aria-label="Loading search results">
  <span class="spinner"></span>
</div>

Automated Testing

Automated tools catch approximately 30% of accessibility issues. The rest require manual testing with keyboard navigation and screen readers.

// jest-axe: automated accessibility testing in unit tests
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('login form has no accessibility violations', async () => {
  const { container } = render(<LoginForm />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});
ToolTypeWhat It Catches
axe-coreAutomated (unit/integration)Missing alt text, contrast, ARIA errors
LighthouseAutomated (browser)WCAG A/AA violations
Pa11yCI pipelineAutomated WCAG testing per page
Keyboard testingManualFocus traps, missing interactions
Screen reader (VoiceOver, NVDA)ManualContent order, announcements, labels

Testing Checklist (Manual)

TestHowPass Criteria
Tab through entire pagePress Tab repeatedlyEvery interactive element receives focus in logical order
Activate every controlEnter/Space on buttons, linksAll actions work without mouse
Check focus visibilityTab through and observeFocus indicator is always visible
Use screen readerVoiceOver / NVDAAll content is announced, labels make sense
Zoom to 200%Browser zoomNo content is cut off, no horizontal scrolling
Disable CSSBrowser dev toolsContent is still readable in logical order

Implementation Checklist

  • Use semantic HTML elements (header, nav, main, article, button) instead of divs
  • Add alt text to all images (descriptive for content, empty alt="" for decorative)
  • Label all form inputs with associated <label> elements
  • Ensure 4.5:1 color contrast ratio for all text
  • Make all functionality available via keyboard (no mouse-only interactions)
  • Add visible focus indicators on all interactive elements
  • Manage focus on modal open/close (move focus in, return on close)
  • Use aria-live regions for dynamic content updates
  • Add axe-core to unit tests and Lighthouse to CI pipeline
  • Test with a screen reader monthly (VoiceOver on Mac, NVDA on Windows)
Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →