Accessibility & Inclusive Design
Accessibility is a correctness property of software: a system that excludes users with disabilities is a system with bugs, not a system lacking features.
Overview
Accessibility in computing is the discipline of designing systems, interfaces, and content so that people with a wide range of abilities — visual, motor, auditory, cognitive — can perceive, understand, navigate, and interact with them effectively. It is not a bolt-on feature or a legal checkbox; it is a quality dimension as fundamental as performance, security, or correctness. A screen that is unreadable to a blind user is as broken as a screen that crashes on input.
The field is grounded in two complementary philosophies. The medical model of disability frames disability as a deficit in an individual that must be accommodated. The social model — the one computer scientists should internalize — frames disability as a mismatch between a person's abilities and the environment or technology they encounter. Under this view, it is the technology that is disabled when it cannot accommodate a person, not the other way around. This reframe has profound engineering consequences: accessibility becomes a design responsibility, not a compassion gesture.
Practically, roughly 15–20% of the global population lives with some form of disability (WHO estimates 1.3 billion people). Many more encounter situational disabilities: a person holding a baby cannot use both hands; a person in bright sunlight cannot read low-contrast text; a person in a noisy cafeteria cannot hear audio alerts. Designing for disability therefore designs for every user in constrained conditions. Apple's dictation feature, Google Maps' voice navigation, subtitles on streaming services — all emerged from accessibility work and became universally used.
This module covers the technical anatomy of accessibility: the WCAG standard and its success criteria, the accessibility tree and assistive technology pipeline, ARIA semantics, keyboard navigation contracts, color and contrast, motor-input accommodation, cognitive load, and the legal and ethical landscape. By the end, you will understand accessibility as a systems engineering problem with measurable correctness criteria — one of the most human-facing forms of engineering a computer scientist can practice.
Accessibility matters at every layer of the software stack. At the platform level, operating systems expose accessibility APIs (AT-SPI on Linux, UIAutomation on Windows, NSAccessibility on macOS) that form a parallel information channel alongside the visual rendering pipeline. If your application does not publish correct semantic metadata into this channel, assistive technologies — screen readers, switch controls, voice control software — receive garbage and cannot help their users. This is a systems programming problem as much as a UX problem.
At the societal and professional level, accessibility is increasingly a legal requirement. The Americans with Disabilities Act (ADA), Section 508 of the US Rehabilitation Act, the EU Web Accessibility Directive, and the UK Equality Act all impose enforceable obligations on software. The WCAG 2.1 standard at Level AA is the de facto legal threshold in most jurisdictions; shipping inaccessible software exposes organizations to litigation, fines, and reputational damage. Beyond compliance, accessible systems expand the addressable user base, improve SEO (search engines behave somewhat like screen readers), and frequently improve the experience for all users — the "curb-cut effect" named for how sidewalk ramps built for wheelchair users turned out to benefit cyclists, delivery workers, and parents with strollers. Engineering accessibility into a system from the start costs roughly 30% of what retrofitting it costs later — another reason it belongs in architecture, not afterthought.
Accessibility Pipeline: From HTML to User
Two parallel pipelines run from HTML: the render tree drives the visual display, while the accessibility tree is exposed to the OS accessibility API and consumed by assistive technologies. Errors in the accessibility tree break the AT pipeline completely, even when the visual display looks correct.
The concepts
01The Disability Landscape and the Social Model
Disabilities relevant to software design are conventionally grouped into four categories:
- Visual — blindness, low vision, color-vision deficiency (~8% of males have some form of color-blindness), photosensitivity.
- Motor/Physical — paralysis, tremor, fine-motor impairment, repetitive strain, limb differences. Users may rely on keyboards only, switch access, eye-gaze systems, sip-and-puff devices, or voice control.
- Auditory — deafness, hard-of-hearing, auditory processing disorders.
- Cognitive/Neurological — dyslexia, ADHD, autism spectrum, memory impairment, anxiety, seizure disorders.
The critical insight of the social model: disability is a relationship between a person and their environment. A deaf person is not disabled by a silent world; they are disabled by a world that communicates exclusively through sound. A blind person is not disabled by information; they are disabled by information presented exclusively through vision. The engineer's job is to eliminate the mismatch.
The social model also introduces situational disabilities: a broken arm creates a temporary motor disability; a loud environment creates a temporary auditory disability; a sleep-deprived developer reading documentation creates a temporary cognitive disability. Designing for permanent disability therefore improves the experience across a continuum of human states.
Universal Design (Mace, 1997) codifies this into seven principles: equitable use, flexibility in use, simple and intuitive use, perceptible information, tolerance for error, low physical effort, and size and space for approach. These principles precede the web but map directly onto software: equitable use means your feature must work for everyone, not just the "average" user.
02The Accessibility Tree and Assistive Technology Pipeline
When a browser renders HTML, it builds two parallel structures: the DOM tree (the data structure) and the render tree (layout and paint). A third structure — the accessibility tree — is built from the DOM and exposed to the operating system's accessibility API. This is the channel through which screen readers, voice control, and other assistive technologies (ATs) perceive the UI.
Each node in the accessibility tree has:
- Role — what kind of thing it is (button, link, checkbox, heading, landmark)
- Name — the human-readable label (computed from visible text,
aria-label,aria-labelledby, oraltattributes) - State — dynamic properties (checked/unchecked, expanded/collapsed, focused)
- Value — current value for form controls (slider at 42%, text field content)
- Description — supplementary description (
aria-describedby)
The pipeline: DOM → Accessibility Tree → OS Accessibility API → AT (screen reader) → user. At each link, information can be lost. A <div onclick="..."> has role generic and no name — the screen reader receives nothing useful. A <button>Save</button> has role button, name Save, and state enabled — the screen reader can announce "Save, button" and inform the user it is interactable.
The key engineering insight: the accessibility tree is a public API your application exposes. Shipping a broken accessibility tree is the same as shipping a broken REST API — your software is unusable by its consumers.
<!-- Inaccessible: div with click handler -->
<div onclick="submitForm()">Save</div>
<!-- AT sees: role=generic, no name, not focusable -->
<!-- Accessible: native button -->
<button type="submit">Save</button>
<!-- AT sees: role=button, name="Save", focusable, activatable -->
<!-- When native semantics are unavailable, use ARIA -->
<div role="button" tabindex="0"
aria-label="Save document"
onclick="submitForm()"
onkeydown="if(e.key==='Enter'||e.key===' ')submitForm()">
Save
</div>
03WCAG: The Correctness Specification
The Web Content Accessibility Guidelines (WCAG), published by the W3C Web Accessibility Initiative (WAI), is the closest thing the field has to a formal specification of accessible software. WCAG 2.1 (2018) and 2.2 (2023) are structured around four principles, abbreviated POUR:
- Perceivable — information must be presentable in forms users can perceive. No information should be perceptible only by sight, sound, or touch.
- Operable — UI components and navigation must be operable. No interaction should require a capability the user lacks.
- Understandable — information and UI operation must be understandable. No content should be beyond the user's comprehension capacity.
- Robust — content must be robust enough for interpretation by assistive technologies, now and in the future.
Each principle contains guidelines (broad goals), and each guideline contains success criteria — testable, binary pass/fail statements. Criteria are leveled:
- Level A — minimum. Failing these causes complete exclusion for some users.
- Level AA — standard. Required by most laws. Failing these causes significant barriers.
- Level AAA — enhanced. Best practice, not universally achievable.
Examples of critical success criteria:
- 1.1.1 Non-text Content (A) — all non-text content (images, icons, charts) has a text alternative.
- 1.4.3 Contrast (Minimum) (AA) — text has at least 4.5:1 contrast ratio against its background (3:1 for large text ≥ 18pt).
- 2.1.1 Keyboard (A) — all functionality is operable via keyboard alone.
- 2.4.7 Focus Visible (AA) — keyboard focus indicator is visible.
- 4.1.2 Name, Role, Value (A) — all UI components have accessible name, role, and state.
WCAG is not perfect — it is primarily oriented toward web content and has blind spots for complex interactive applications and native mobile — but it is the baseline every engineer must know.
# WCAG 1.4.3: Contrast ratio computation
# Relative luminance per WCAG 2.x formula
def relative_luminance(r8, g8, b8):
"""r8,g8,b8 in [0,255]"""
def linearize(c):
s = c / 255.0
return s / 12.92 if s <= 0.04045 else ((s + 0.055) / 1.055) ** 2.4
R, G, B = linearize(r8), linearize(g8), linearize(b8)
return 0.2126 * R + 0.7152 * G + 0.0722 * B
def contrast_ratio(color1_rgb, color2_rgb):
L1 = relative_luminance(*color1_rgb)
L2 = relative_luminance(*color2_rgb)
lighter, darker = max(L1, L2), min(L1, L2)
return (lighter + 0.05) / (darker + 0.05)
# Example: black text on white background
print(contrast_ratio((0,0,0), (255,255,255))) # 21.0 — perfect
# Mid-gray on white
print(contrast_ratio((118,118,118), (255,255,255))) # ~4.5 — AA pass
# Light gray on white — common failure
print(contrast_ratio((180,180,180), (255,255,255))) # ~2.3 — FAIL
04Semantic HTML and ARIA
The most powerful accessibility tool is HTML used semantically. Native HTML elements carry built-in accessibility semantics that browsers expose to the accessibility tree for free. <button> is focusable, activatable with Enter and Space, and announced as a button. <nav>, <main>, <header>, <footer>, <aside> create landmark regions that screen reader users navigate between like chapters. <h1>–<h6> create a document outline. <table> with <th scope="col"> creates grid relationships screen readers can traverse. Using semantic HTML correctly costs nothing extra and gives you accessibility correctness almost for free.
WAI-ARIA (Accessible Rich Internet Applications) is a supplement for cases where native HTML semantics are insufficient — primarily custom interactive widgets. ARIA provides:
- Roles (
role="dialog",role="tablist",role="combobox") — override or assign semantic role - Properties (
aria-label,aria-labelledby,aria-describedby) — provide naming and description - States (
aria-expanded,aria-checked,aria-disabled,aria-live) — expose dynamic state
The first rule of ARIA (official W3C guidance): do not use ARIA if you can use a native HTML element or attribute that already provides the correct semantics. ARIA is surgical, not a replacement for semantic HTML.
ARIA live regions (aria-live="polite" or aria-assertive") tell screen readers to announce dynamic updates without focus movement — essential for single-page applications, toast notifications, and form validation messages.
The ARIA Authoring Practices Guide (APG) published by W3C documents the keyboard interaction and ARIA patterns for every standard widget: accordions, date pickers, dialogs, menus, sliders. Following the APG is the engineering specification for custom component accessibility.
<!-- Landmark regions — free document navigation for AT users -->
<header role="banner">...</header>
<nav aria-label="Primary navigation">...</nav>
<main id="main-content">...</main>
<aside aria-label="Related articles">...</aside>
<footer role="contentinfo">...</footer>
<!-- Custom tab widget following ARIA APG pattern -->
<div role="tablist" aria-label="Product sections">
<button role="tab" id="tab-1"
aria-selected="true"
aria-controls="panel-1">Overview</button>
<button role="tab" id="tab-2"
aria-selected="false"
aria-controls="panel-2"
tabindex="-1">Details</button>
</div>
<div role="tabpanel" id="panel-1"
aria-labelledby="tab-1">...</div>
<div role="tabpanel" id="panel-2"
aria-labelledby="tab-2" hidden>...</div>
<!-- Live region for dynamic updates -->
<div aria-live="polite" aria-atomic="true"
class="sr-only" id="status-msg">
<!-- JS inserts: "Form saved successfully" -->
</div>
05Keyboard Navigation and Focus Management
Keyboard accessibility is criterion 2.1.1 in WCAG and the foundational motor-accessibility requirement. Every user who cannot use a pointing device — blind users navigating by screen reader, motor-impaired users using keyboards or switch access, power users — depends on keyboard navigation. A feature that requires a mouse is a feature that excludes these users entirely.
The keyboard contract for web UIs:
- Tab / Shift+Tab — move focus forward / backward through interactive elements
- Arrow keys — navigate within composite widgets (menus, tabs, radio groups, grids)
- Enter — activate buttons, links, submit forms
- Space — toggle checkboxes, activate buttons
- Escape — close dialogs, cancel operations, collapse expanded widgets
Tab order should follow the visual reading order (top-to-bottom, left-to-right in LTR languages). tabindex="0" adds an element to the natural tab order. tabindex="-1" makes an element programmatically focusable (via JS .focus()) without entering the tab order. Never use positive tabindex values — they create a separate tab order sequence that creates confusion.
Focus management is critical in single-page applications (SPAs). When a dialog opens, focus must move into it; when it closes, focus must return to the trigger element. When a page navigation occurs (without a browser reload), focus should move to the new page heading or content area. Failing to manage focus leaves screen reader users lost in the old location with no indication that the UI has changed.
Focus trap is a pattern used in dialogs: while the dialog is open, Tab and Shift+Tab cycle only within the dialog, preventing focus from escaping to background content. This is required by WCAG 2.1 Success Criterion 2.1.2 (No Keyboard Trap) — the intent is that trapping must be escapable (via Escape key), not permanent.
// Focus trap implementation for modal dialogs
function trapFocus(dialogEl) {
const focusable = dialogEl.querySelectorAll(
'a[href], button:not([disabled]), textarea, input, ' +
'select, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
dialogEl.addEventListener('keydown', function(e) {
if (e.key !== 'Tab') return;
if (e.shiftKey) { // Shift+Tab
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else { // Tab
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
first.focus(); // move focus into dialog on open
}
// Returning focus on dialog close
function openDialog(dialogEl, triggerEl) {
dialogEl.removeAttribute('hidden');
trapFocus(dialogEl);
dialogEl.addEventListener('close', () => {
triggerEl.focus(); // return to trigger
}, { once: true });
}
06Color, Contrast, and Visual Design
Color is one of the most common accessibility failures in software, for two independent reasons:
1. Contrast ratio: text must have sufficient luminance contrast against its background to be readable by people with low vision. WCAG 1.4.3 requires 4.5:1 for normal text and 3:1 for large text. The contrast ratio formula uses relative luminance — a gamma-corrected, luminance-weighted sum of linearized RGB channels (see the code in the WCAG concept above). Perceived brightness is not linear in RGB; #808080 gray on white has a contrast ratio of 3.95:1, which fails AA for body text despite looking like it should be fine.
2. Color-only information (WCAG 1.4.1): information must not be conveyed through color alone. Charts that use only red vs. green lines to distinguish data series exclude the 8% of men with red-green color-vision deficiency. Solutions: add labels, use distinct line styles (solid/dashed), add patterns, use symbols, or select a colorblind-safe palette (e.g., Paul Tol's or Wong's color sets). Form validation that turns a field red on error must also show an error icon and text, not just change the border color.
Focus indicators are a specific contrast problem. WCAG 2.2 Success Criterion 2.4.11 requires that the focus indicator have a minimum area of the perimeter of the unfocused element and a minimum contrast of 3:1 between focused and unfocused states. Many default browser focus rings pass this; the widespread developer practice of outline: none in CSS is an accessibility disaster.
Reflow and zoom: WCAG 1.4.4 requires that text can be resized to 200% without loss of content or functionality. WCAG 1.4.10 (Reflow, AA in 2.1) requires that content can be presented without horizontal scrolling at 320 CSS pixels width — equivalent to 400% zoom on a 1280px-wide display. This forces responsive design as an accessibility requirement, not merely a mobile optimization.
/* Accessible focus indicator — never remove outlines */
/* Instead of outline: none, make it beautiful */
:focus-visible {
outline: 3px solid #0066CC;
outline-offset: 2px;
border-radius: 2px;
}
/* Hide outlines for mouse users but keep for keyboard */
/* :focus-visible is only set when focus arrives via keyboard */
:focus:not(:focus-visible) {
outline: none;
}
/* Reflow: no fixed widths that break at 320px */
.card {
width: 100%; /* not width: 400px */
max-width: 400px;
padding: 1rem;
box-sizing: border-box;
}
/* Color-blind safe: never use color alone for data */
.chart-line-a {
stroke: #E69F00; /* orange — visible to deuteranopes */
stroke-dasharray: none;
}
.chart-line-b {
stroke: #56B4E9; /* sky blue */
stroke-dasharray: 5 3; /* ALSO use dash pattern */
}
07Screen Readers: How They Work
A screen reader is a piece of software that translates the accessibility tree into synthesized speech or braille output. Understanding how screen readers work is essential for building software they can use correctly.
Major screen readers: NVDA (Windows, free, open source), JAWS (Windows, commercial), VoiceOver (macOS/iOS, built-in), TalkBack (Android, built-in), Orca (Linux/GNOME).
Screen readers have two modes:
1. Browse mode (virtual cursor): The screen reader constructs a linearized text representation of the page and lets the user navigate it line by line, character by character, or by element type (skip to next heading, next link, next form control). This is how users read documents. In browse mode, keystrokes like H (next heading), F (next form field), and T (next table) are consumed by the screen reader, not the page.
2. Application/forms mode: When the user focuses on an interactive widget (a text field, a custom slider), the screen reader hands keystrokes through to the application and the user interacts directly with the widget. Switching between modes is automatic on native HTML elements but must be explicitly triggered for custom ARIA widgets via role="application" (use sparingly — it disables all browse-mode navigation shortcuts).
Screen readers also process live regions (aria-live): when the DOM inside a live region changes, the screen reader queues the new text for announcement without focus movement. polite waits for the user to finish their current task; assertive interrupts immediately (use only for critical errors).
Common screen reader bugs engineers introduce:
- Putting important information only in CSS
::before/::afterpseudo-elements (often invisible to AT) - Using
display:noneorvisibility:hiddento visually hide content that should still be announced (use the visually hidden class pattern instead) - Inserting nodes into the DOM before the user is ready, causing screen readers to read partial content
- Using SVG icons without accessible names or titles
/* Visually-hidden class: hides content visually
but keeps it in the accessibility tree */
.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;
}
/* Usage: icon buttons need a visible label for AT */
08Forms, Errors, and Cognitive Accessibility
Forms are the most interaction-dense surfaces in software and the site of the most accessibility failures. Four areas matter most:
1. Label association: Every <input>, <textarea>, and <select> must have a programmatically associated label — either <label for="id"> or aria-label or aria-labelledby. Placeholder text is not a label; it disappears on input and has insufficient contrast. The label must identify the purpose of the field.
2. Error identification and suggestion (WCAG 3.3.1, 3.3.3): When a form error is detected, the field in error must be identified (not just outlined in red), the nature of the error must be described in text, and — where possible — a correction suggestion must be provided. The error message must be associated with the field via aria-describedby so screen reader users hear it when they reach the field.
3. Success criteria for autocomplete (WCAG 1.3.5): Input fields collecting personal information (name, email, address, phone) must have the correct autocomplete attribute values so browsers and ATs can auto-fill them. This is critical for users with cognitive and motor disabilities who find typing difficult.
4. Cognitive accessibility: Reading level, consistent navigation, predictable behavior, error prevention, and sufficient time are WCAG criteria rooted in cognitive disability but universally beneficial. Key practices: use plain language (aim for 8th-grade reading level for general audiences), provide confirmation dialogs for destructive actions, allow users to review and correct before submitting, do not impose time limits that cannot be extended.
WCAG 2.2 additions (2023) specifically address cognitive accessibility: SC 3.2.6 Consistent Help (AA), SC 3.3.7 Redundant Entry (A) — don't make users re-enter information they already provided — and SC 3.3.8 Accessible Authentication (AA) — don't require cognitive tests (CAPTCHAs, memory tasks) as the only authentication path.
<!-- Fully accessible form field with error handling -->
<div class="field-group">
<label for="email">Email address</label>
<!-- Note: label is NOT inside input as placeholder -->
<input
type="email"
id="email"
name="email"
autocomplete="email"
aria-required="true"
aria-describedby="email-hint email-error"
aria-invalid="true"
/>
<p id="email-hint" class="hint">
We will use this to send your receipt.
</p>
<!-- Error: only shown when validation fails -->
<p id="email-error" class="error" role="alert">
<!-- role=alert makes it a live region -->
Enter a valid email address, for example
name@example.com
</p>
</div>
09Native Mobile Accessibility
Web accessibility principles translate to native mobile platforms, but the APIs differ. Both iOS and Android expose accessibility trees through platform APIs, and the patterns — roles, names, states, actions — are the same concepts with different syntax.
iOS / SwiftUI: accessibility modifiers attach semantic metadata to views. .accessibilityLabel() sets the name. .accessibilityHint() provides supplementary description. .accessibilityValue() exposes the current value. .accessibilityAddTraits() sets role (e.g., .isButton, .isHeader). Custom actions beyond the default tap are registered with .accessibilityAction(). VoiceOver users swipe to navigate between elements; focus order can be controlled with .accessibilitySortPriority() and .accessibilityElement(children: .combine).
Android / Jetpack Compose: Modifier.semantics { } is the accessibility API. contentDescription sets the name. role = Role.Button sets the role. stateDescription exposes state. onClick within semantics registers keyboard/switch actions. TalkBack is the primary screen reader.
Touch target size (WCAG 2.5.5 / 2.5.8): interactive elements must be at least 24×24 CSS pixels (2.5.8, AA in WCAG 2.2) or preferably 44×44 points (Apple HIG) / 48×48dp (Material Design) to accommodate users with tremor or limited fine-motor control. Small touch targets are one of the most common and easiest-to-fix mobile accessibility failures.
Gesture alternatives: custom swipe gestures must have accessible alternatives. Any feature accessible only via a multi-finger gesture, force touch, or motion sensor must also be accessible via standard single-touch or switch interactions.
// iOS SwiftUI — accessible custom progress indicator
struct DownloadProgress: View {
var progress: Double // 0.0 to 1.0
var body: some View {
ProgressView(value: progress)
.accessibilityLabel("Download progress")
.accessibilityValue(
"\(Int(progress * 100)) percent complete"
)
// Remove the visual ProgressView from
// the accessibility tree so only our
// custom semantics are used:
.accessibilityElement(children: .ignore)
}
}
// Android Jetpack Compose — accessible icon button
@Composable
fun DeleteButton(onClick: () -> Unit) {
IconButton(onClick = onClick,
modifier = Modifier
.size(48.dp) // minimum 48dp touch target
.semantics {
role = Role.Button
contentDescription = "Delete item"
}
) {
Icon(Icons.Default.Delete,
contentDescription = null) // null: name on parent
}
}
10Testing and Tooling
Accessibility, like security, cannot be fully automated — but automated tools catch a significant fraction of issues and should be part of every CI pipeline.
Automated tools:
- axe-core (Deque Systems): the most widely used accessibility engine, with integrations for Jest, Playwright, Cypress, Storybook, and browser DevTools. Catches ~57% of WCAG issues automatically (Deque's own estimate).
- Lighthouse (Google): built into Chrome DevTools; runs axe-core for accessibility; reports score and criterion violations.
- WAVE (WebAIM): browser extension that annotates the page with accessibility information.
- ESLint plugin jsx-a11y: static analysis for React components — catches missing alt text, missing form labels, incorrect ARIA usage at write time.
Manual testing checklist:
- Keyboard-only: tab through the entire page, verify all interactive elements are reachable and operable, verify focus is always visible.
- Zoom to 200%: verify no content is lost or overlapping.
- Screen reader: navigate with NVDA+Firefox or VoiceOver+Safari. Verify all content is announced correctly, all interactive elements have meaningful names, all dynamic updates are announced.
- Color-contrast checker: measure contrast of all text/background combinations.
- Disable CSS: verify content is readable and logically ordered without styling.
Automated in CI with Playwright + axe-core:
// Playwright + axe-core: accessibility test in CI
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.describe('Homepage accessibility', () => {
test('should have no WCAG 2.1 AA violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
.analyze();
// Print violations for debugging:
if (results.violations.length > 0) {
console.table(results.violations.map(v => ({
id: v.id,
impact: v.impact,
description: v.description,
nodes: v.nodes.length
})));
}
expect(results.violations).toEqual([]);
});
});
11Legal, Ethical, and Engineering Culture
Legal landscape: In the United States, the ADA Title III has been interpreted by courts and the DOJ to apply to websites and mobile applications. Section 508 of the Rehabilitation Act requires federal agencies and federally funded organizations to make their electronic content accessible to WCAG 2.0 AA at minimum. The EU Web Accessibility Directive (2016/2102/EU) and the European Accessibility Act (2019/882/EU) mandate WCAG 2.1 AA compliance for public sector and many private sector services. The UK Equality Act 2010 has similar reach. Consequences of non-compliance include injunctions, fines, and settlements; major companies including Domino's Pizza, Netflix, and Target have faced high-profile accessibility lawsuits.
Ethical framing: The ACM Code of Ethics (Principle 1.1) requires computing professionals to contribute to society and human well-being. Shipping inaccessible software that excludes 15% of users is a direct violation of this principle. It is not a gray area. The profession's ethical obligation is identical to the engineer's obligation to ship correct, secure software — accessibility is a dimension of correctness.
Engineering culture: Retrofitting accessibility into a completed product costs an estimated 3–10× more than building it in from the start. The engineering argument for accessibility is the same as for security or testing: shift left. Accessibility should be:
- Part of design acceptance criteria
- Part of component library contracts (every component in the design system ships with correct ARIA and keyboard behavior)
- Part of code review
- Part of CI (axe-core or equivalent)
- Part of QA (screen reader and keyboard manual testing)
The curb-cut effect is the empirical validation of universal design: features built for accessibility regularly become universally valuable. Closed captions were built for deaf users; they are now used at scale in noisy environments and by people learning languages. Voice control was built for motor-impaired users; it became Siri and Alexa. Keyboard shortcuts built for accessibility are loved by power users. Accessible forms with good error messages reduce abandonment rates for everyone. Accessibility is not a tax — it is engineering quality.
Key insights
- ✦The accessibility tree is a public API. Shipping a broken accessibility tree is as serious as shipping a broken REST API — your software is unusable by its consumers.
- ✦The social model of disability locates the problem in the environment, not the person. This makes accessibility a design correctness problem, not a charity problem.
- ✦WCAG success criteria are binary pass/fail tests — accessibility has measurable, verifiable correctness criteria just like unit tests.
- ✦The first rule of ARIA: do not use ARIA when a native HTML element already provides the correct semantics. Semantic HTML is free accessibility.
- ✦Contrast ratio is computed on perceptual luminance, not raw RGB values — #808080 gray on white fails WCAG AA for body text despite appearing adequate.
- ✦Focus management is a required engineering contract in SPAs: dialogs must trap focus, route changes must move focus to new content, and closing dialogs must return focus to the trigger.
- ✦Automated tools (axe-core, Lighthouse) catch roughly half of WCAG issues; the other half requires manual keyboard and screen reader testing — both are required.
- ✦The curb-cut effect makes accessibility a universal design win: captions, voice control, and keyboard shortcuts built for disability became universally used features.
Common pitfalls
- ⚠Using `outline: none` or `outline: 0` in CSS without providing an alternative focus indicator — this makes keyboard navigation invisible for millions of users.
- ⚠Communicating state or information through color alone (red = error, green = success) without additional textual or iconic cues, which excludes color-blind users.
- ⚠Using placeholder text as a substitute for a `<label>` element — placeholder text has insufficient contrast, disappears on input, and is not reliably announced by all screen readers.
- ⚠Forgetting to manage focus in SPAs — not moving focus into dialogs on open, not returning focus to the trigger on close, not announcing route changes to screen readers.
- ⚠Applying `aria-label` to elements that already have visible text, creating a mismatch between what sighted users see and what screen readers announce (violates WCAG 2.5.3).
- ⚠Using positive `tabindex` values (tabindex="5") to create a custom tab order — this creates a separate, confusing navigation sequence; use DOM order and `tabindex="0"` or `-1` only.
- ⚠Assuming automated accessibility testing is sufficient — axe-core catches ~50-57% of issues; keyboard and screen reader manual testing are mandatory.
- ⚠Treating accessibility as a phase to be added after development is complete — retrofitting costs 3-10x what building it in from the start costs.
Practice
- 01easy
Audit a web page you use daily (your university portal, a news site, a shopping site) using only the keyboard. Tab through the entire page. Document: (1) every interactive element you cannot reach with Tab, (2) every point where focus becomes invisible, (3) any modal or dialog that does not trap focus correctly. Write up your findings as a structured accessibility bug report.
- 02hard
Implement a fully accessible custom dropdown/combobox widget from scratch in HTML, CSS, and JavaScript. It must: (a) announce its role and current value to screen readers, (b) open and close with Enter/Space/Alt+Down, (c) navigate options with Arrow keys, (d) close with Escape and return focus to the trigger, (e) filter options as the user types. Validate with axe-core and test with VoiceOver or NVDA.
- 03medium
Write a Python script that reads an image file and computes the contrast ratio between every pair of text-colored and background-colored pixels (you may approximate by sampling). Report whether each pair passes WCAG AA for normal text (4.5:1), large text (3:1), or fails. Test it on a screenshot of a real webpage.
- 04medium
Take a React or Vue component library component (a Button, a Modal, or a Select) and write a formal accessibility specification for it: list every ARIA role, every required attribute, every optional attribute, every keyboard interaction, the expected screen reader announcement on focus, and the expected announcement on state change. Then verify your specification against the W3C APG.
- 05medium
Set up an axe-core integration in a Playwright or Cypress test suite for an existing web project (your own, or a public open-source project). Run it against at least three pages and produce a report of all violations found. For each violation, (a) identify the WCAG success criterion it violates, (b) explain what user impact it causes, and (c) write the fix.
Resources
The normative specification. Every engineer building software for the web must read at least the four POUR principles and the Level A and AA success criteria.
The engineering specification for accessible interactive widgets: keyboard interactions, ARIA patterns, and live examples for every standard component type.
Practical tutorials, contrast checker, screen reader guides, and the annual WebAIM Million report benchmarking real-world accessibility failures.
The most readable book-length treatment of accessible design, grounded in real user research. Goes beyond checklists into design thinking.
Practical, component-by-component guide to accessible HTML/CSS/JS patterns. Outstanding engineering-level reference.
The professional certification standard for accessibility (IAAP WAS exam prep). Deque also produces axe-core and the browser DevTools axe extension.
The industry-standard open-source accessibility testing engine. Integrations for Jest, Playwright, Cypress, Storybook, and browser DevTools.
MDN Accessibility documentation is among the best technical references for understanding the DOM-to-AT pipeline, ARIA, and semantic HTML.
MIT OCW course covering HCI, accessibility, and interface design with lecture notes and materials.
Wikipedia's treatment of the social model of disability — the foundational philosophical framework that reframes disability as an environmental mismatch.
Checkpoints — you can now…
- ✓You can explain the difference between the medical model and the social model of disability, and articulate why the social model matters for how engineers approach accessibility.
- ✓You can describe the accessibility tree, explain what information each node contains (role, name, state, value), and explain why a clickable div is inaccessible while a button is not.
- ✓You can identify which WCAG 2.1 level (A, AA, AAA) a given success criterion belongs to, and explain what POUR stands for and what each principle covers.
- ✓You can compute the WCAG contrast ratio between two RGB colors from first principles, and determine whether a text/background pair passes Level AA.
- ✓You can write a fully accessible custom interactive widget (tab panel, modal dialog, or combobox) including correct ARIA attributes, keyboard interaction, and focus management.
- ✓You can set up an axe-core test in a CI pipeline and interpret the output to identify specific WCAG failures and their user impact.
- ✓You can articulate why accessibility is a legal, ethical, and engineering concern, and name at least three examples of the curb-cut effect where accessibility features became universally valuable.