Introduction to Web Accessibility
Imagine arriving at a building with no ramps, only stairs, and doors too narrow for wheelchairs. For people with mobility impairments, this physical barrier prevents them from accessing the building. Web accessibility is about ensuring that similar barriers don't exist in the digital world, making websites and applications usable by everyone, regardless of their abilities or the methods they use to interact with digital content.
Accessibility (often abbreviated as "a11y" - the 11 representing the number of letters between 'a' and 'y') is not just a nice-to-have feature or a legal checkbox to tick. It's a fundamental aspect of web development that acknowledges the diverse ways people experience and interact with digital content. Just as physical spaces should accommodate people with diverse abilities, digital spaces should be designed with the same inclusivity in mind.
In this session, you'll learn:
- Why web accessibility matters from ethical, legal, and business perspectives
- The primary accessibility standards and guidelines (WCAG, WAI-ARIA, etc.)
- Common accessibility barriers and how to address them
- Practical techniques for implementing accessible web features
- Tools and methods for testing accessibility
- How accessibility fits into your overall development workflow
Why Accessibility Matters
Accessibility isn't just about accommodating people with permanent disabilities. Consider these scenarios:
- A person with a broken arm temporarily using a keyboard instead of a mouse
- An older adult with declining vision who needs larger text
- Someone in a noisy environment who can't hear audio content
- A person using a slow internet connection who benefits from optimized page loads
- Someone with cognitive disabilities who needs clear, consistent navigation
When we design for accessibility, we're designing for all these situations and more. The improvements we make benefit everyone, not just users with disabilities.
Three Key Motivations for Accessibility
Ethical Considerations
The web was designed as a universal platform, available to all regardless of hardware, software, language, location, or ability. When we build inaccessible websites, we're contradicting this foundational principle and potentially excluding millions of users. The World Health Organization estimates that over one billion people (about 15% of the world's population) live with some form of disability.
Building accessible websites aligns with the principles of digital inclusion and equal opportunity, acknowledging that access to information and services is increasingly a fundamental right in our digital society.
Legal Requirements
In many countries, accessibility is mandated by law:
- United States: The Americans with Disabilities Act (ADA) and Section 508 of the Rehabilitation Act apply to many websites
- European Union: The European Accessibility Act and various national laws require digital accessibility
- United Kingdom: The Equality Act covers website accessibility
- Canada: The Accessible Canada Act addresses digital inclusion
- Australia: The Disability Discrimination Act applies to web content
Legal requirements vary by region, but the trend is toward stronger enforcement of digital accessibility. Non-compliance can lead to lawsuits, financial penalties, and damage to reputation.
Business Benefits
Accessibility is good for business for several reasons:
- Expanded Market: Accessible websites reach more potential customers
- Improved Brand Perception: Demonstrates corporate social responsibility
- Enhanced User Experience: Many accessibility features improve usability for all users
- Better SEO: Many accessibility practices align with search engine optimization
- Reduced Legal Risk: Proactive accessibility reduces the risk of litigation
- Innovation: Designing for diverse needs often leads to creative solutions
Key Accessibility Standards and Guidelines
Several organizations have developed standards and guidelines for web accessibility. Understanding these frameworks is essential for creating accessible web content.
Web Content Accessibility Guidelines (WCAG)
WCAG is the most widely recognized standard for web accessibility, developed by the World Wide Web Consortium (W3C) through their Web Accessibility Initiative (WAI). Currently at version 2.1 (with 2.2 in development), WCAG provides a comprehensive framework for making web content accessible.
Four Core Principles of WCAG
WCAG is organized around four principles, often remembered by the acronym "POUR":
Perceivable
Information and user interface components must be presentable to users in ways they can perceive. This means providing text alternatives for non-text content, captions for multimedia, creating content that can be presented in different ways, and making it easier for users to see and hear content.
Example: Providing alt text for images so screen reader users can understand the image content.
Operable
User interface components and navigation must be operable. This means making all functionality available from a keyboard, giving users enough time to read and use content, not designing content in a way that is known to cause seizures, and providing ways to help users navigate and find content.
Example: Ensuring all interactive elements can be accessed and activated using keyboard navigation.
Understandable
Information and the operation of the user interface must be understandable. This means making text readable and understandable, making content appear and operate in predictable ways, and helping users avoid and correct mistakes.
Example: Using clear labels for form fields and providing helpful error messages when users make mistakes.
Robust
Content must be robust enough to be interpreted reliably by a wide variety of user agents, including assistive technologies. This means maximizing compatibility with current and future user tools.
Example: Writing valid HTML that properly uses semantic elements and ARIA attributes when needed.
WCAG Conformance Levels
WCAG defines three levels of conformance:
- Level A: The most basic web accessibility features
- Level AA: Addresses the biggest and most common barriers for disabled users (this is the level most organizations aim for and is often referenced in laws and policies)
- Level AAA: The highest level of accessibility
Accessible Rich Internet Applications (ARIA)
WAI-ARIA is a set of attributes that define ways to make web content and applications more accessible. These attributes supplement HTML to provide additional semantics about the roles, states, and properties of user interface elements.
ARIA is particularly useful for dynamic content and advanced user interface controls developed with JavaScript and HTML. It helps bridge the gaps in HTML's native accessibility features, especially for complex interactive components.
ARIA Categories
Roles
Define what an element is or does, such as role="navigation", role="button", or role="alert".
<div role="button" tabindex="0" onclick="activateButton()">
Click Me
</div>
States
Define the current condition of an element, such as aria-checked="true", aria-disabled="true", or aria-expanded="false".
<button aria-expanded="false" onclick="toggleMenu()">
Menu
</button>
Properties
Define additional semantics or relationships, such as aria-label, aria-labelledby, or aria-describedby.
<input type="text" aria-describedby="password-requirements">
<div id="password-requirements">
Password must be at least 8 characters and include at least one number
</div>
A Word of Caution About ARIA
The first rule of ARIA is: Don't use ARIA if you can use a native HTML element with the semantics and behavior you require. ARIA should be used to enhance accessibility, not replace proper HTML semantics.
For example, instead of:
<div role="button" tabindex="0" onclick="activateButton()">
Click Me
</div>
It's better to use:
<button onclick="activateButton()">
Click Me
</button>
Other Notable Standards
Section 508
Section 508 of the Rehabilitation Act requires U.S. federal agencies to make their electronic and information technology accessible to people with disabilities. The current version of Section 508 incorporates WCAG 2.0 Level AA success criteria.
EN 301 549
The European standard for digital accessibility requirements, which also references WCAG. It applies to public sector websites and applications in the European Union.
Semantic HTML: The Foundation of Accessibility
Using semantic HTML is one of the most important practices for web accessibility. Semantic HTML uses elements that convey meaning about the structure and content of a web page, providing valuable information to assistive technologies and improving the overall accessibility of your site.
Semantic vs. Non-Semantic Elements
Non-Semantic Elements
Elements like <div> and <span> don't inherently convey any meaning about their content. They're generic containers.
Semantic Elements
Elements like <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>, and <button> describe the purpose or role of the content they contain.
Compare These Two Approaches
Non-Semantic Structure
<div class="header">
<div class="logo">My Website</div>
<div class="nav">
<div class="nav-item"><div class="link" onclick="navigate('home')">Home</div></div>
<div class="nav-item"><div class="link" onclick="navigate('about')">About</div></div>
<div class="nav-item"><div class="link" onclick="navigate('contact')">Contact</div></div>
</div>
</div>
<div class="main-content">
<div class="article">
<div class="article-title">Article Title</div>
<div class="article-meta">Posted on January 1, 2025</div>
<div class="article-content">
<div class="paragraph">This is the first paragraph.</div>
<div class="paragraph">This is the second paragraph.</div>
</div>
</div>
</div>
<div class="footer">
<div class="copyright">Copyright 2025</div>
</div>
Semantic Structure
<header>
<h1>My Website</h1>
<nav>
<ul>
<li><a href="home.html">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>Article Title</h2>
<time datetime="2025-01-01">Posted on January 1, 2025</time>
<div class="content">
<p>This is the first paragraph.</p>
<p>This is the second paragraph.</p>
</div>
</article>
</main>
<footer>
<p>Copyright 2025</p>
</footer>
Benefits of Semantic HTML
- Accessibility: Screen readers and other assistive technologies can interpret the page structure and content more accurately
- SEO: Search engines better understand the content and its importance
- Maintainability: Code is more readable and easier to maintain
- Device compatibility: Content adapts better to different devices and contexts
- Future-proofing: More resilient to changes in browsers and standards
Key Semantic Elements and Their Uses
Headings (<h1> through <h6>)
Headings create a hierarchical structure for your content. Screen reader users often navigate pages by headings, so a proper heading structure is crucial.
<h1>Main Page Title</h1>
<section>
<h2>Section Title</h2>
<p>Section content...</p>
<h3>Subsection Title</h3>
<p>Subsection content...</p>
</section>
Key practices:
- Use only one
<h1>per page, typically for the main title - Don't skip heading levels (e.g., don't go from
<h2>directly to<h4>) - Don't choose heading levels based solely on styling preferences (use CSS to adjust appearance if needed)
Landmarks (<header>, <nav>, <main>, <footer>, etc.)
Landmark elements identify common regions of a page, making it easier for users of assistive technologies to navigate between major sections.
<body>
<header>
<h1>My Website</h1>
<nav>
<!-- Navigation content -->
</nav>
</header>
<main>
<!-- Main content -->
</main>
<aside>
<!-- Complementary content -->
</aside>
<footer>
<!-- Footer content -->
</footer>
</body>
Lists (<ul>, <ol>, <dl>)
Lists group related items and provide additional context to screen reader users.
<!-- Unordered list for navigation -->
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<!-- Ordered list for steps -->
<ol>
<li>Create an account</li>
<li>Verify your email</li>
<li>Complete your profile</li>
</ol>
<!-- Description list for terms and definitions -->
<dl>
<dt>HTML</dt>
<dd>HyperText Markup Language, used to structure web content</dd>
<dt>CSS</dt>
<dd>Cascading Style Sheets, used to style web content</dd>
</dl>
Buttons and Links
Use the right element for the job: links (<a>) navigate to new resources, while buttons (<button>) perform actions.
<!-- Link example - navigates to another page -->
<a href="product-details.html">View Product Details</a>
<!-- Button example - performs an action -->
<button type="button" onclick="addToCart()">Add to Cart</button>
Forms and Labels
Properly structured forms with labels make form interaction accessible.
<form>
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<fieldset>
<legend>Subscription Options</legend>
<div class="radio-option">
<input type="radio" id="monthly" name="subscription" value="monthly">
<label for="monthly">Monthly</label>
</div>
<div class="radio-option">
<input type="radio" id="annual" name="subscription" value="annual">
<label for="annual">Annual</label>
</div>
</fieldset>
<button type="submit">Subscribe</button>
</form>
Key form practices:
- Always use
<label>elements properly associated with their controls - Group related form controls with
<fieldset>and<legend> - Provide clear error messages and validation feedback
- Use appropriate input types (
type="email",type="tel", etc.)
Tables
Use tables for tabular data, not for layout, and include proper headers.
<table>
<caption>Monthly Expenses</caption>
<thead>
<tr>
<th scope="col">Category</th>
<th scope="col">January</th>
<th scope="col">February</th>
<th scope="col">March</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Rent</th>
<td>$1,500</td>
<td>$1,500</td>
<td>$1,500</td>
</tr>
<tr>
<th scope="row">Utilities</th>
<td>$250</td>
<td>$280</td>
<td>$240</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>$1,750</td>
<td>$1,780</td>
<td>$1,740</td>
</tr>
</tfoot>
</table>
Advanced Accessibility with ARIA
While semantic HTML should be your first choice, ARIA (Accessible Rich Internet Applications) attributes can enhance accessibility when HTML alone isn't sufficient. Let's explore common ARIA use cases and best practices.
When to Use ARIA
Use ARIA when:
- You need to create custom interactive components that don't have native HTML equivalents
- You're working with dynamic content that updates without page reloads
- You need to add additional information or relationships not conveyed by HTML semantics
- You need to support legacy code where completely replacing with semantic HTML isn't feasible
Common ARIA Patterns
Accessible Custom Dropdown
<div class="custom-dropdown">
<button id="dropdown-btn" aria-haspopup="true" aria-expanded="false">
Select an option
</button>
<ul id="dropdown-menu" role="menu" aria-labelledby="dropdown-btn" hidden>
<li role="menuitem" tabindex="-1">Option 1</li>
<li role="menuitem" tabindex="-1">Option 2</li>
<li role="menuitem" tabindex="-1">Option 3</li>
</ul>
</div>
<script>
const btn = document.getElementById('dropdown-btn');
const menu = document.getElementById('dropdown-menu');
const items = menu.querySelectorAll('[role="menuitem"]');
// Toggle menu
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', !expanded);
menu.hidden = expanded;
if (!expanded) {
// Focus first item when opening
items[0].focus();
}
});
// Handle keyboard navigation
menu.addEventListener('keydown', (e) => {
const currentIndex = Array.from(items).indexOf(document.activeElement);
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
const nextIndex = Math.min(currentIndex + 1, items.length - 1);
items[nextIndex].focus();
break;
case 'ArrowUp':
e.preventDefault();
const prevIndex = Math.max(currentIndex - 1, 0);
items[prevIndex].focus();
break;
case 'Escape':
e.preventDefault();
btn.setAttribute('aria-expanded', 'false');
menu.hidden = true;
btn.focus();
break;
}
});
</script>
Live Regions for Dynamic Content
ARIA live regions announce changes to screen reader users without requiring focus changes.
<!-- Polite live region (waits for user idle) -->
<div aria-live="polite" aria-atomic="true" class="notification">
<!-- Content will be injected here -->
</div>
<!-- Assertive live region (interrupts) -->
<div aria-live="assertive" aria-atomic="true" class="alert">
<!-- Content will be injected here -->
</div>
<script>
// Example: Show notification
function showNotification(message) {
const notificationArea = document.querySelector('.notification');
notificationArea.textContent = message;
// Clear after 5 seconds
setTimeout(() => {
notificationArea.textContent = '';
}, 5000);
}
// Example: Show critical alert
function showAlert(message) {
const alertArea = document.querySelector('.alert');
alertArea.textContent = message;
}
</script>
Accessible Tabs
<div class="tabs">
<div role="tablist" aria-label="Project Information">
<button id="tab-1" role="tab" aria-selected="true" aria-controls="panel-1">
Description
</button>
<button id="tab-2" role="tab" aria-selected="false" aria-controls="panel-2" tabindex="-1">
Specifications
</button>
<button id="tab-3" role="tab" aria-selected="false" aria-controls="panel-3" tabindex="-1">
Reviews
</button>
</div>
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1">
<h3>Description</h3>
<p>Product description content...</p>
</div>
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>
<h3>Specifications</h3>
<p>Product specifications content...</p>
</div>
<div id="panel-3" role="tabpanel" aria-labelledby="tab-3" hidden>
<h3>Reviews</h3>
<p>Product reviews content...</p>
</div>
</div>
<script>
const tabs = document.querySelectorAll('[role="tab"]');
const panels = document.querySelectorAll('[role="tabpanel"]');
// Add click event to each tab
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// Deselect all tabs
tabs.forEach(t => {
t.setAttribute('aria-selected', 'false');
t.setAttribute('tabindex', '-1');
});
// Hide all panels
panels.forEach(panel => {
panel.hidden = true;
});
// Select clicked tab
tab.setAttribute('aria-selected', 'true');
tab.removeAttribute('tabindex');
// Show corresponding panel
const panelId = tab.getAttribute('aria-controls');
document.getElementById(panelId).hidden = false;
});
});
// Add keyboard navigation
const tabList = document.querySelector('[role="tablist"]');
tabList.addEventListener('keydown', (e) => {
// Get the index of the current tab
const currentTab = document.activeElement;
const currentIndex = Array.from(tabs).indexOf(currentTab);
// Handle arrow keys
if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
e.preventDefault();
let newIndex;
if (e.key === 'ArrowRight') {
newIndex = (currentIndex + 1) % tabs.length;
} else {
newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
}
// Focus and activate the new tab
tabs[newIndex].focus();
tabs[newIndex].click();
}
});
</script>
Form Validation and Error Messages
<form id="signup-form">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" aria-describedby="email-hint"
aria-required="true">
<div id="email-hint" class="hint">Enter your email address</div>
<div id="email-error" class="error" aria-live="polite"></div>
</div>
<button type="submit">Sign Up</button>
</form>
<script>
const form = document.getElementById('signup-form');
const emailInput = document.getElementById('email');
const emailError = document.getElementById('email-error');
// Validate on submission
form.addEventListener('submit', (e) => {
let isValid = true;
// Clear previous errors
emailError.textContent = '';
emailInput.removeAttribute('aria-invalid');
// Validate email
if (!emailInput.value) {
e.preventDefault();
isValid = false;
emailError.textContent = 'Email is required';
emailInput.setAttribute('aria-invalid', 'true');
} else if (!isValidEmail(emailInput.value)) {
e.preventDefault();
isValid = false;
emailError.textContent = 'Please enter a valid email address';
emailInput.setAttribute('aria-invalid', 'true');
}
if (!isValid) {
// Focus the first invalid field
emailInput.focus();
}
});
function isValidEmail(email) {
return /\S+@\S+\.\S+/.test(email);
}
</script>
Common ARIA Mistakes to Avoid
- Redundant ARIA: Don't add ARIA roles that duplicate the native semantics of HTML elements
- Conflicting ARIA: Don't override the native semantics of elements with contradictory ARIA
- Missing required attributes: Some ARIA roles require specific attributes
- Not updating ARIA states: If you use
aria-expanded,aria-selected, etc., be sure to update them when the state changes - Overusing ARIA: Use native HTML elements whenever possible before reaching for ARIA
Incorrect vs. Correct ARIA Usage
<button role="button">Click me</button>
<h1 role="button">This is confusing!</h1>
<div role="combobox">
<input type="text">
<ul>
<li>Option 1</li>
<li>Option 2</li>
</ul>
</div>
<div>
<label id="combo-label">Select a fruit:</label>
<input type="text" role="combobox" aria-expanded="false"
aria-controls="listbox1" aria-labelledby="combo-label"
aria-autocomplete="list">
<ul id="listbox1" role="listbox" hidden>
<li role="option" id="option1">Apple</li>
<li role="option" id="option2">Banana</li>
<li role="option" id="option3">Orange</li>
</ul>
</div>
Testing for Accessibility
Testing is a crucial part of ensuring web accessibility. A comprehensive testing approach involves both automated tools and manual testing methods.
Automated Testing Tools
Automated tools can identify many common accessibility issues quickly, but they typically catch only about 30-40% of potential problems. Use them as a first line of defense, not a complete solution.
Browser Extensions
- axe DevTools: Comprehensive accessibility testing in the browser
- WAVE: Visual feedback about accessibility issues within your page
- Lighthouse: Built into Chrome DevTools, includes accessibility audits
- IBM Equal Access Accessibility Checker: Detailed accessibility evaluations
Command Line Tools
- axe-core: The JavaScript library that powers many testing tools
- Pa11y: Runs accessibility tests and generates reports
- jest-axe: Add accessibility tests to Jest test suites
// Example: Using axe-core in JavaScript
const axe = require('axe-core');
// Run accessibility tests
axe.run(document, (err, results) => {
if (err) throw err;
// Log violations
if (results.violations.length) {
console.error('Accessibility violations found:', results.violations);
} else {
console.log('No accessibility violations detected!');
}
});
CI/CD Integration
Integrate accessibility testing into your continuous integration pipeline to catch issues automatically.
// Example GitHub Action for accessibility testing
name: Accessibility Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
accessibility:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Run accessibility tests
run: npm run test:a11y
Manual Testing Methods
Manual testing is essential for a complete accessibility evaluation. Some aspects of accessibility simply cannot be detected by automated tools.
Keyboard Navigation Testing
Test that all functionality is available using only the keyboard.
- Press Tab to navigate through interactive elements
- Verify that focus order is logical and follows the visual layout
- Ensure focus indicators are visible at all times
- Check that you can activate all controls with Enter or Space
- Verify that you can close dialogs with Esc
- Test keyboard shortcuts if implemented
Screen Reader Testing
Use screen readers to experience your site as blind or low vision users would.
- NVDA or JAWS: Popular screen readers for Windows
- VoiceOver: Built into macOS and iOS
- TalkBack: Built into Android devices
Testing considerations:
- Can you understand the page structure?
- Are images described appropriately?
- Is form completion straightforward?
- Do dynamic updates get announced?
- Is the reading order logical?
Color and Contrast Testing
- Use color contrast checkers to verify WCAG compliance
- Test with color blindness simulators to ensure information isn't lost
- Verify that the site is usable in high contrast mode
Content Scaling
- Test with browser zoom at 200%
- Verify that content doesn't get cut off or overlap
- Check that the layout adapts appropriately
User Testing
Nothing replaces testing with actual users who have disabilities. Consider:
- Recruiting users with different disabilities
- Observing how they use your site
- Gathering feedback on barriers they encounter
Accessibility Testing Checklist
- Content:
- Do all images have appropriate alt text?
- Are headings structured properly?
- Is text content clear and understandable?
- Are link purposes clear from their text?
- Keyboard:
- Can you access all functionality with keyboard only?
- Is the focus order logical?
- Are focus states clearly visible?
- Are there any keyboard traps?
- Forms:
- Are all form controls properly labeled?
- Are required fields indicated clearly?
- Are error messages clear and accessible?
- Are related form elements grouped logically?
- Visual:
- Does all text meet contrast requirements?
- Is information conveyed by more than just color?
- Does the site work in high contrast mode?
- Is content readable when zoomed to 200%?
- Structure:
- Does the HTML use semantic elements appropriately?
- Are ARIA roles, states, and properties used correctly?
- Is the page title descriptive and unique?
- Does the page have appropriate landmark regions?
- Multimedia:
- Do videos have captions?
- Is audio content available as text transcripts?
- Can media playback be controlled by the keyboard?
- Is auto-playing content avoided or controllable?
Integrating Accessibility into Development Workflow
Accessibility is most effective and efficient when integrated throughout the development process rather than treated as an afterthought. Here's how to incorporate accessibility at different stages:
Planning and Requirements
- Define accessibility requirements based on WCAG levels (typically AA)
- Include accessibility in acceptance criteria for user stories
- Consider different user scenarios, including users with disabilities
- Budget time for accessibility testing and remediation
Design Phase
- Verify color contrast meets WCAG requirements
- Design clear focus indicators
- Plan logical tab order and reading flow
- Create text alternatives for visual content
- Design responsive layouts that work with text resizing
- Document accessibility requirements for interactive components
Development Phase
- Start with semantic HTML to establish a solid foundation
- Implement keyboard navigation early
- Use linters and automated testing tools in your development environment
- Create component libraries with accessibility built-in
- Perform regular reviews of code for accessibility issues
- Document accessibility features and requirements
Example: Adding Accessibility Checks to Your Build Process
// package.json
{
"scripts": {
"lint:a11y": "eslint --ext .js,.jsx,.ts,.tsx src/ --config .eslintrc-a11y.js",
"test:a11y": "jest --testMatch='**/*.a11y.test.js'",
"prebuild": "npm run lint:a11y && npm run test:a11y"
}
}
// .eslintrc-a11y.js
module.exports = {
extends: [
'plugin:jsx-a11y/recommended'
],
plugins: [
'jsx-a11y'
],
rules: {
'jsx-a11y/alt-text': 'error',
'jsx-a11y/aria-role': 'error',
'jsx-a11y/aria-props': 'error',
'jsx-a11y/aria-proptypes': 'error',
'jsx-a11y/aria-unsupported-elements': 'error',
'jsx-a11y/no-static-element-interactions': 'error'
}
}
Example: Automated Accessibility Testing
// example.a11y.test.js
import { axe } from 'jest-axe';
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';
describe('Accessibility tests', () => {
it('should not have accessibility violations', async () => {
const { container } = render(<MyComponent />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
QA and Testing Phase
- Include accessibility testing in the QA process
- Train QA teams on accessibility testing techniques
- Incorporate both automated and manual testing
- Document and track accessibility issues in your bug tracking system
- Prioritize accessibility fixes based on impact and severity
Maintenance and Updates
- Maintain an accessibility statement that documents the current state of accessibility
- Establish a process for users to report accessibility issues
- Regularly audit existing content and features for accessibility
- Stay current with updates to accessibility standards and techniques
- Track and improve accessibility metrics over time
Team Education and Resources
- Provide accessibility training for all team members
- Designate accessibility champions within each discipline
- Share resources and best practices across the team
- Encourage team members to experience using assistive technologies
- Celebrate accessibility wins and improvements
Mobile-Specific Accessibility Considerations
Mobile devices present unique accessibility challenges and opportunities. Here are key considerations for mobile accessibility:
Touch Targets
Small touch targets can be difficult for users with motor impairments or those with larger fingers.
- Make touch targets at least 44×44 pixels (WCAG recommends a minimum target size of 44×44 CSS pixels)
- Provide adequate spacing between touch targets to prevent accidental activations
- Ensure that the clickable area extends to the entire element, not just the visible part (e.g., text or icon)
/* CSS for appropriately sized touch targets */
.button {
min-width: 44px;
min-height: 44px;
padding: 12px 16px;
}
/* Spacing between touch targets */
.nav-item {
margin: 8px;
}
/* Extending touch target area beyond the visible content */
.icon-button {
position: relative;
}
.icon-button::after {
content: '';
position: absolute;
top: -8px;
right: -8px;
bottom: -8px;
left: -8px;
/* Makes the touch target larger without affecting visual appearance */
}
Gesture Alternatives
Complex touch gestures (pinch, swipe, multi-finger gestures) can be challenging for some users.
- Provide alternative methods for gesture-based interactions
- Include visible buttons for actions that can also be performed by gestures
- Make gesture-based functionality discoverable through visible interface elements
// JavaScript example showing alternative to swipe gestures
const gallery = document.querySelector('.image-gallery');
const prevButton = document.querySelector('.prev-button');
const nextButton = document.querySelector('.next-button');
// Event listeners for alternative button controls
prevButton.addEventListener('click', showPreviousImage);
nextButton.addEventListener('click', showNextImage);
// Touch swipe events
let touchStartX = 0;
let touchEndX = 0;
gallery.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
});
gallery.addEventListener('touchend', (e) => {
touchEndX = e.changedTouches[0].screenX;
handleSwipe();
});
function handleSwipe() {
if (touchEndX < touchStartX - 50) {
// Swipe left, show next image
showNextImage();
} else if (touchEndX > touchStartX + 50) {
// Swipe right, show previous image
showPreviousImage();
}
}
function showPreviousImage() {
// Code to show previous image
}
function showNextImage() {
// Code to show next image
}
Device Orientation
Users with mobility impairments may have their devices fixed in a specific orientation.
- Design interfaces that work well in both portrait and landscape orientations
- Don't require orientation changes to access essential functionality
- Test your application in both orientations
/* CSS for orientation responsiveness */
@media screen and (orientation: portrait) {
.content-container {
flex-direction: column;
}
}
@media screen and (orientation: landscape) {
.content-container {
flex-direction: row;
}
}
Software Keyboards
Software keyboards can take up significant screen space and affect layout.
- Ensure forms are still usable when the software keyboard is visible
- Make sure important buttons aren't hidden by the keyboard
- Use appropriate input types to trigger the right keyboard type (e.g., tel, email, number)
<!-- HTML with appropriate input types -->
<input type="email" id="email" placeholder="Email Address">
<input type="tel" id="phone" placeholder="Phone Number">
<input type="number" id="age" min="0" max="120" placeholder="Age">
/* CSS to handle keyboard visibility */
.form-container {
padding-bottom: 60px; /* Space for keyboard and submit button */
}
.sticky-submit {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 10px;
background-color: white;
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
z-index: 100;
}
Responsive Text and Layout
Ensure text remains readable on small screens and with larger text sizes.
- Use relative units (em, rem) for text and container sizes
- Test with text enlarged to 200%
- Ensure layouts reflow rather than requiring horizontal scrolling
/* Responsive text sizing */
html {
font-size: 16px; /* Base size */
}
body {
font-size: 1rem;
line-height: 1.5;
}
h1 {
font-size: 2rem; /* 32px at default size */
}
h2 {
font-size: 1.5rem; /* 24px at default size */
}
@media screen and (max-width: 480px) {
html {
font-size: 14px; /* Slightly smaller base on very small screens */
}
}
JavaScript and Dynamic Content Accessibility
Modern web applications rely heavily on JavaScript for interactivity. Here are key considerations for making JavaScript-heavy applications accessible:
Progressive Enhancement
Build a solid foundation that works without JavaScript, then enhance with JavaScript when available.
<!-- Form that works without JavaScript -->
<form action="/search" method="get">
<label for="search">Search:</label>
<input type="text" id="search" name="q">
<button type="submit">Search</button>
</form>
<script>
// Enhanced with JavaScript
const form = document.querySelector('form');
const searchInput = document.getElementById('search');
const resultsContainer = document.getElementById('results');
form.addEventListener('submit', function(e) {
// Only prevent default if JavaScript is available
e.preventDefault();
// Fetch results via AJAX
fetchSearchResults(searchInput.value)
.then(results => {
displayResults(results);
});
});
</script>
Focus Management
Proper focus management is essential for keyboard accessibility in dynamic applications.
- Maintain a logical focus order even with dynamically added content
- Return focus to a logical location after user actions
- Use consistent patterns for focus management across the application
// Modal dialog focus management example
const openButton = document.getElementById('open-dialog');
const dialog = document.getElementById('my-dialog');
const closeButton = document.getElementById('close-dialog');
let lastFocusedElement;
// Open dialog and trap focus
openButton.addEventListener('click', () => {
// Store the element that had focus before opening
lastFocusedElement = document.activeElement;
// Show dialog
dialog.hidden = false;
dialog.setAttribute('aria-hidden', 'false');
// Set focus on the first focusable element in the dialog
const firstFocusable = dialog.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (firstFocusable) {
firstFocusable.focus();
}
// Trap focus within the dialog
dialog.addEventListener('keydown', trapFocus);
});
// Close dialog and restore focus
closeButton.addEventListener('click', () => {
// Hide dialog
dialog.hidden = true;
dialog.setAttribute('aria-hidden', 'true');
// Remove focus trap
dialog.removeEventListener('keydown', trapFocus);
// Restore focus to the element that had focus before opening
if (lastFocusedElement) {
lastFocusedElement.focus();
}
});
// Function to trap focus within the dialog
function trapFocus(e) {
if (e.key !== 'Tab') return;
// Get all focusable elements in the dialog
const focusableElements = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
// If shift+tab on first element, move to last element
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
// If tab on last element, move to first element
else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
Communicating Dynamic Updates
Screen reader users need to be informed of content changes that occur without page reloads.
- Use ARIA live regions for important updates
- Consider the appropriate politeness level (polite, assertive) based on the urgency of the update
- Update page titles when the main content changes significantly
<!-- Live region for status updates -->
<div id="status" aria-live="polite" aria-atomic="true" class="sr-only"></div>
<!-- Live region for error messages -->
<div id="error-container" aria-live="assertive" role="alert" aria-atomic="true" class="sr-only"></div>
// JavaScript to update live regions
function updateStatus(message) {
document.getElementById('status').textContent = message;
}
function showError(message) {
document.getElementById('error-container').textContent = message;
}
// Example usage
async function submitForm() {
try {
updateStatus('Submitting form...');
await sendFormData();
updateStatus('Form submitted successfully');
} catch (error) {
showError('An error occurred. Please try again.');
}
}
Loading States and Error Handling
Clearly communicate loading states and errors to all users.
- Provide visible loading indicators
- Make loading states available to screen readers
- Display clear error messages when things go wrong
- Give guidance on how to resolve errors
<!-- Loading state example -->
<button type="button" id="load-data">
<span class="button-text">Load Data</span>
<span class="loading-indicator" hidden aria-hidden="true">
<!-- Spinner or loading animation -->
</span>
</button>
<div id="loading-status" aria-live="polite"></div>
<div id="error-message" aria-live="assertive" role="alert"></div>
<div id="results-container" aria-live="polite"></div>
// JavaScript to manage loading states
const button = document.getElementById('load-data');
const buttonText = button.querySelector('.button-text');
const loadingIndicator = button.querySelector('.loading-indicator');
const loadingStatus = document.getElementById('loading-status');
const errorMessage = document.getElementById('error-message');
const resultsContainer = document.getElementById('results-container');
button.addEventListener('click', async () => {
// Show loading state
button.disabled = true;
buttonText.textContent = 'Loading...';
loadingIndicator.hidden = false;
loadingIndicator.setAttribute('aria-hidden', 'false');
loadingStatus.textContent = 'Loading data, please wait.';
try {
const data = await fetchData();
// Update UI with results
loadingStatus.textContent = 'Data loaded successfully.';
displayResults(data);
} catch (error) {
// Show error state
errorMessage.textContent = `Error loading data: ${error.message}. Please try again.`;
} finally {
// Reset button state
button.disabled = false;
buttonText.textContent = 'Load Data';
loadingIndicator.hidden = true;
loadingIndicator.setAttribute('aria-hidden', 'true');
}
});
Performance and Accessibility
Performance is an accessibility concern. Slow websites and applications can be particularly challenging for users with cognitive disabilities, those on slower connections, or those using assistive technologies that add additional processing overhead.
Why Performance Matters for Accessibility
- Screen readers and other assistive technologies add processing overhead
- Users with cognitive disabilities may struggle with complex, slow interfaces
- Assistive technology users experience delays compounded by their tools
- Users in rural or developing areas often have slower connections
- Older devices commonly used by people with disabilities may have less processing power
Performance Guidelines for Accessibility
Minimize Page Size and Request Count
- Compress images and other assets
- Use appropriate image formats and sizes
- Bundle and minify CSS and JavaScript
- Implement lazy loading for non-critical resources
Optimize JavaScript Execution
- Minimize main thread blocking
- Break up long tasks into smaller chunks
- Use efficient event listeners and debouncing for scroll/resize events
- Consider using Web Workers for complex computations
// Example of debouncing a search input to prevent performance issues
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const searchInput = document.getElementById('search-input');
const performSearch = debounce(function(query) {
// Perform search operation
console.log(`Searching for: ${query}`);
}, 300);
searchInput.addEventListener('input', function() {
performSearch(this.value);
});
Prioritize Critical Rendering Path
- Load critical CSS inline in the
<head> - Defer non-critical JavaScript
- Use resource hints like preload and prefetch
- Implement server-side rendering for faster initial content
<!-- Critical CSS inlined -->
<head>
<style>
/* Critical CSS for above-the-fold content */
body { font-family: sans-serif; margin: 0; padding: 0; }
header { background: #f5f5f5; padding: 1rem; }
main { padding: 1rem; }
</style>
<!-- Non-critical CSS loaded asynchronously -->
<link rel="preload" href="/css/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/styles.css"></noscript>
<!-- Defer non-critical JavaScript -->
<script src="/js/app.js" defer></script>
</head>
Provide Progress Indicators and Feedback
- Show loading states for operations that take more than 1 second
- Use progress indicators for multi-step processes
- Provide clear feedback when operations complete
- Allow users to cancel long operations when possible
Test on Low-End Devices and Slow Connections
- Use throttling in browser developer tools to simulate slow connections
- Test on actual low-end devices if possible
- Consider using performance budgets to maintain speed
- Monitor performance metrics over time
Practical Examples: Common Components
Let's explore accessible implementations of common UI components that you'll likely need to build in your web applications.
Accessible Navigation Menu
A responsive navigation menu with dropdown support and proper keyboard navigation.
<!-- HTML Structure -->
<nav aria-label="Main Navigation">
<button id="menu-toggle" aria-expanded="false" aria-controls="menu-list" aria-label="Toggle menu" hidden>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<ul id="menu-list">
<li><a href="/">Home</a></li>
<li class="has-submenu">
<a href="/products" id="products-toggle" aria-haspopup="true" aria-expanded="false">
Products
<span class="dropdown-icon" aria-hidden="true">▼</span>
</a>
<ul class="submenu" aria-labelledby="products-toggle">
<li><a href="/products/category1">Category 1</a></li>
<li><a href="/products/category2">Category 2</a></li>
<li><a href="/products/category3">Category 3</a></li>
</ul>
</li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
/* CSS for responsive navigation */
nav {
position: relative;
}
#menu-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.submenu {
display: none;
position: absolute;
list-style: none;
background: white;
padding: 0.5rem 0;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
min-width: 200px;
z-index: 100;
}
.has-submenu:hover .submenu,
.has-submenu:focus-within .submenu {
display: block;
}
/* Accessible focus styles */
a:focus {
outline: 2px solid #4a90e2;
outline-offset: 2px;
}
/* Responsive styles */
@media (max-width: 768px) {
#menu-toggle {
display: block;
}
#menu-list {
display: none;
flex-direction: column;
width: 100%;
}
#menu-list.open {
display: flex;
}
.submenu {
position: static;
box-shadow: none;
padding-left: 1rem;
}
}
// JavaScript for interaction
document.addEventListener('DOMContentLoaded', function() {
const menuToggle = document.getElementById('menu-toggle');
const menuList = document.getElementById('menu-list');
const subMenuToggles = document.querySelectorAll('[aria-haspopup="true"]');
// Toggle mobile menu
menuToggle.addEventListener('click', function() {
const expanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !expanded);
menuList.classList.toggle('open');
});
// Handle submenu toggles
subMenuToggles.forEach(toggle => {
toggle.addEventListener('click', function(e) {
// Only handle click for mobile or keyboard users
if (window.innerWidth <= 768 || e.detail === 0) {
e.preventDefault();
const expanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !expanded);
// Find and toggle the submenu
const submenu = this.nextElementSibling;
submenu.style.display = expanded ? 'none' : 'block';
}
});
// Handle keyboard navigation
toggle.addEventListener('keydown', function(e) {
const submenu = this.nextElementSibling;
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
const expanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !expanded);
submenu.style.display = expanded ? 'none' : 'block';
if (!expanded) {
// Focus the first item in the submenu
submenu.querySelector('a').focus();
}
} else if (e.key === 'Escape' && this.getAttribute('aria-expanded') === 'true') {
this.setAttribute('aria-expanded', 'false');
submenu.style.display = 'none';
this.focus();
}
});
});
});
Accessible Modal Dialog
A modal dialog with proper focus management and keyboard interaction.
<!-- HTML Structure -->
<button id="open-dialog" type="button">Open Dialog</button>
<div id="dialog-overlay" class="dialog-overlay" hidden></div>
<div id="dialog" role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-description" aria-modal="true" hidden>
<div class="dialog-header">
<h2 id="dialog-title">Confirmation Required</h2>
<button type="button" id="close-dialog" aria-label="Close dialog">×</button>
</div>
<div class="dialog-body">
<p id="dialog-description">Are you sure you want to continue with this action?</p>
</div>
<div class="dialog-footer">
<button type="button" id="cancel-action">Cancel</button>
<button type="button" id="confirm-action" class="primary">Confirm</button>
</div>
</div>
/* CSS for Modal Dialog */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
#dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1001;
background: white;
padding: 1.5rem;
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
max-width: 500px;
width: 90%;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
#dialog-title {
margin: 0;
}
#close-dialog {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0.5rem;
}
.dialog-footer {
margin-top: 1.5rem;
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
// JavaScript for Modal Dialog
document.addEventListener('DOMContentLoaded', function() {
const openButton = document.getElementById('open-dialog');
const closeButton = document.getElementById('close-dialog');
const cancelButton = document.getElementById('cancel-action');
const confirmButton = document.getElementById('confirm-action');
const dialog = document.getElementById('dialog');
const overlay = document.getElementById('dialog-overlay');
let previouslyFocused = null;
// Open dialog
openButton.addEventListener('click', function() {
// Store the element that had focus before opening
previouslyFocused = document.activeElement;
// Show dialog and overlay
dialog.hidden = false;
overlay.hidden = false;
// Focus the first focusable element in the dialog
closeButton.focus();
// Add event listeners for keyboard handling
document.addEventListener('keydown', handleEscape);
dialog.addEventListener('keydown', trapFocus);
});
// Close handlers
function closeDialog() {
dialog.hidden = true;
overlay.hidden = true;
// Restore focus to the element that had focus before opening
if (previouslyFocused) {
previouslyFocused.focus();
}
// Remove event listeners
document.removeEventListener('keydown', handleEscape);
dialog.removeEventListener('keydown', trapFocus);
}
closeButton.addEventListener('click', closeDialog);
cancelButton.addEventListener('click', closeDialog);
confirmButton.addEventListener('click', function() {
// Handle confirmation action here
// Then close dialog
closeDialog();
});
// Close on background click
overlay.addEventListener('click', closeDialog);
// Prevent clicks inside the dialog from closing it
dialog.addEventListener('click', function(e) {
e.stopPropagation();
});
// Close on Escape key
function handleEscape(e) {
if (e.key === 'Escape') {
closeDialog();
}
}
// Trap focus within the dialog
function trapFocus(e) {
if (e.key !== 'Tab') return;
// Get all focusable elements
const focusableElements = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey) {
// Shift+Tab: if focus is on first element, move to last
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
// Tab: if focus is on last element, move to first
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
});
Accessible Form with Validation
A form with proper labels, error handling, and validation feedback.
<!-- HTML Structure -->
<form id="registration-form" novalidate>
<div class="form-header">
<h2>Create an Account</h2>
<p>All fields marked with an asterisk (*) are required</p>
</div>
<div class="form-group">
<label for="name">
Name *
</label>
<input type="text" id="name" name="name" required
aria-describedby="name-error name-hint">
<div id="name-hint" class="hint">Enter your full name</div>
<div id="name-error" class="error" aria-live="polite"></div>
</div>
<div class="form-group">
<label for="email">
Email Address *
</label>
<input type="email" id="email" name="email" required
aria-describedby="email-error email-hint">
<div id="email-hint" class="hint">We'll never share your email with anyone else</div>
<div id="email-error" class="error" aria-live="polite"></div>
</div>
<div class="form-group">
<label for="password">
Password *
</label>
<input type="password" id="password" name="password" required
aria-describedby="password-error password-requirements">
<div id="password-requirements" class="hint">
Password must be at least 8 characters and include at least one number and one special character
</div>
<div id="password-error" class="error" aria-live="polite"></div>
</div>
<fieldset>
<legend>Communication Preferences</legend>
<div class="checkbox-group">
<input type="checkbox" id="newsletter" name="newsletter">
<label for="newsletter">Subscribe to our newsletter</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="updates" name="updates">
<label for="updates">Receive product updates</label>
</div>
</fieldset>
<div class="form-group">
<button type="submit" id="submit-button">Create Account</button>
</div>
</form>
<div id="form-errors-summary" class="error-summary" role="alert" aria-live="assertive" hidden>
<h3>Please correct the following errors:</h3>
<ul id="errors-list"></ul>
</div>
/* CSS for Form */
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #ccc;
border-radius: 4px;
}
.hint {
margin-top: 0.25rem;
font-size: 0.875rem;
color: #666;
}
.error {
margin-top: 0.25rem;
font-size: 0.875rem;
color: #d32f2f;
font-weight: bold;
}
input.invalid {
border-color: #d32f2f;
}
fieldset {
margin: 1.5rem 0;
border: 1px solid #ccc;
padding: 1rem;
border-radius: 4px;
}
.checkbox-group {
margin-bottom: 0.5rem;
}
.checkbox-group label {
display: inline;
margin-left: 0.5rem;
}
.error-summary {
border: 2px solid #d32f2f;
background-color: #ffebee;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1.5rem;
}
.error-summary h3 {
margin-top: 0;
color: #d32f2f;
}
.error-summary ul {
margin-bottom: 0;
}
button[type="submit"] {
padding: 0.75rem 1.5rem;
background-color: #0066cc;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
}
button[type="submit"]:hover {
background-color: #0052a3;
}
button[type="submit"]:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
// JavaScript for Form Validation
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('registration-form');
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const submitButton = document.getElementById('submit-button');
const nameError = document.getElementById('name-error');
const emailError = document.getElementById('email-error');
const passwordError = document.getElementById('password-error');
const errorsSummary = document.getElementById('form-errors-summary');
const errorsList = document.getElementById('errors-list');
// Helper function to display validation errors
function showError(input, errorElement, message) {
input.classList.add('invalid');
input.setAttribute('aria-invalid', 'true');
errorElement.textContent = message;
}
// Helper function to clear validation errors
function clearError(input, errorElement) {
input.classList.remove('invalid');
input.removeAttribute('aria-invalid');
errorElement.textContent = '';
}
// Real-time validation
nameInput.addEventListener('input', function() {
if (this.value.trim() === '') {
showError(this, nameError, 'Name is required');
} else {
clearError(this, nameError);
}
});
emailInput.addEventListener('input', function() {
if (this.value.trim() === '') {
showError(this, emailError, 'Email is required');
} else if (!isValidEmail(this.value)) {
showError(this, emailError, 'Please enter a valid email address');
} else {
clearError(this, emailError);
}
});
passwordInput.addEventListener('input', function() {
if (this.value.trim() === '') {
showError(this, passwordError, 'Password is required');
} else if (this.value.length < 8) {
showError(this, passwordError, 'Password must be at least 8 characters long');
} else if (!hasNumberAndSpecial(this.value)) {
showError(this, passwordError, 'Password must include at least one number and one special character');
} else {
clearError(this, passwordError);
}
});
// Form submission
form.addEventListener('submit', function(e) {
e.preventDefault();
// Clear previous errors
clearErrors();
// Perform validation
let isValid = true;
const errors = [];
// Validate name
if (nameInput.value.trim() === '') {
isValid = false;
showError(nameInput, nameError, 'Name is required');
errors.push('Name is required');
}
// Validate email
if (emailInput.value.trim() === '') {
isValid = false;
showError(emailInput, emailError, 'Email is required');
errors.push('Email is required');
} else if (!isValidEmail(emailInput.value)) {
isValid = false;
showError(emailInput, emailError, 'Please enter a valid email address');
errors.push('Please enter a valid email address');
}
// Validate password
if (passwordInput.value.trim() === '') {
isValid = false;
showError(passwordInput, passwordError, 'Password is required');
errors.push('Password is required');
} else if (passwordInput.value.length < 8) {
isValid = false;
showError(passwordInput, passwordError, 'Password must be at least 8 characters long');
errors.push('Password must be at least 8 characters long');
} else if (!hasNumberAndSpecial(passwordInput.value)) {
isValid = false;
showError(passwordInput, passwordError, 'Password must include at least one number and one special character');
errors.push('Password must include at least one number and one special character');
}
// Display errors summary if there are errors
if (!isValid) {
showErrorsSummary(errors);
// Focus the first invalid field
if (nameInput.classList.contains('invalid')) {
nameInput.focus();
} else if (emailInput.classList.contains('invalid')) {
emailInput.focus();
} else if (passwordInput.classList.contains('invalid')) {
passwordInput.focus();
}
} else {
// Form is valid, submit it
// In a real application, you would submit the form data here
alert('Form submitted successfully!');
form.reset();
}
});
// Helper functions
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function hasNumberAndSpecial(password) {
return /[0-9]/.test(password) && /[!@#$%^&*(),.?":{}|<>]/.test(password);
}
function clearErrors() {
clearError(nameInput, nameError);
clearError(emailInput, emailError);
clearError(passwordInput, passwordError);
errorsSummary.hidden = true;
errorsList.innerHTML = '';
}
function showErrorsSummary(errors) {
errorsList.innerHTML = '';
errors.forEach(function(error) {
const li = document.createElement('li');
li.textContent = error;
errorsList.appendChild(li);
});
errorsSummary.hidden = false;
}
});
Conclusion and Next Steps
Accessibility is a fundamental aspect of web development, not an optional extra. It's about ensuring that your web applications can be used by everyone, regardless of their abilities or the technologies they use to access the web.
Key Takeaways
- Accessibility benefits everyone, not just users with disabilities
- Start with semantic HTML and follow established patterns
- Test early and often with a variety of techniques
- Address accessibility throughout the development process
- Remember that perfect accessibility is a journey, not a destination
Additional Resources
Remember that accessibility is not about checking boxes or meeting minimum requirements—it's about creating an inclusive web that can be used by everyone. By incorporating these principles and techniques into your development process, you're not just complying with standards or avoiding legal issues; you're building a better web for all users.
In our afternoon session, we'll build on these concepts with hands-on exercises and review real-world examples of accessibility improvements.