Why Accessibility Matters
1 in 4 adults has a disability. Accessible websites reach more users, improve SEO, and often have better overall UX. It's also the law in many jurisdictions.
Semantic HTML First
Article Title
Article Title
Accessible Forms
Keyboard Navigation
// Custom dropdown with keyboard support
class AccessibleDropdown {
constructor(element) {
this.container = element;
this.button = element.querySelector('[role="button"]');
this.menu = element.querySelector('[role="menu"]');
this.items = element.querySelectorAll('[role="menuitem"]');
this.currentIndex = 0;
this.bindEvents();
}
bindEvents() {
this.button.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
e.preventDefault();
this.open();
this.focusItem(0);
}
});
this.menu.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
this.focusItem(this.currentIndex + 1);
break;
case 'ArrowUp':
e.preventDefault();
this.focusItem(this.currentIndex - 1);
break;
case 'Escape':
this.close();
this.button.focus();
break;
case 'Home':
this.focusItem(0);
break;
case 'End':
this.focusItem(this.items.length - 1);
break;
}
});
}
focusItem(index) {
this.currentIndex = Math.max(0, Math.min(index, this.items.length - 1));
this.items[this.currentIndex].focus();
}
}ARIA Patterns
Confirm Action
Are you sure you want to proceed?
...
...
Item added to cart
Focus Management
// Focus trap for modals
function trapFocus(element) {
const focusable = element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
element.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
first.focus();
}Color Contrast
/* Ensure sufficient contrast ratios */
:root {
/* WCAG AA: 4.5:1 for normal text, 3:1 for large text */
--text-primary: #1f2937; /* Good contrast on white */
--text-secondary: #4b5563; /* Still meets AA */
--text-muted: #6b7280; /* Use sparingly, large text only */
/* Focus indicators must be visible */
--focus-ring: 0 0 0 3px rgba(59, 130, 246, 0.5);
}
/* Never rely on color alone */
.error {
color: var(--color-error);
border-left: 4px solid var(--color-error);
}
.error::before {
content: 'โ ';
}Testing for Accessibility
// Automated testing with jest-axe
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('form has no accessibility violations', async () => {
const { container } = render();
const results = await axe(container);
expect(results).toHaveNoViolations();
}); Conclusion
Accessibility is a feature, not an afterthought. Start with semantic HTML, add ARIA only when needed, ensure keyboard support, and test with real assistive technologies.