HTML Accessibility (ARIA Basics)
Accessibility ensures everyone can use your website — including people with disabilities. ARIA (Accessible Rich Internet Applications) helps bridge gaps when HTML alone isn’t enough.
Why Accessibility?
- Legal requirement — Many countries mandate accessibility
- Better SEO — Search engines read accessible code
- Wider audience — 15% of people have disabilities
- Better UX — Accessible sites work better for everyone
Semantic HTML First
Always use semantic HTML before ARIA:
<!-- Bad: Using div with ARIA -->
<div role="button" tabindex="0" onclick="handleClick()">
Click me
</div>
<!-- Good: Using native HTML -->
<button onclick="handleClick()">Click me</button>
Why? Semantic HTML has built-in accessibility. ARIA is a supplement, not a replacement.
ARIA Roles
Roles define what an element is or does:
<!-- Landmark roles -->
<div role="banner">Header content</div>
<div role="navigation">Nav links</div>
<div role="main">Main content</div>
<div role="contentinfo">Footer content</div>
<!-- Widget roles -->
<div role="button">Custom button</div>
<div role="tab">Tab item</div>
<div role="tabpanel">Tab content</div>
<div role="alert">Important message</div>
Common roles:
banner— Site headernavigation— Nav linksmain— Main contentcontentinfo— Footercomplementary— Sidebarsearch— Search functionalitybutton— Interactive buttontab— Tab interfacetabpanel— Tab contentalert— Important messagesdialog— Modal dialogprogressbar— Progress indicator
ARIA Labels
Labels describe elements for screen readers:
aria-label
Provides an accessible name:
<button aria-label="Close menu">×</button>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
<nav aria-label="Footer navigation">
<ul>
<li><a href="/privacy">Privacy</a></li>
</ul>
</nav>
aria-labelledby
References another element’s text:
<h2 id="section-title">Our Services</h2>
<div aria-labelledby="section-title">
<p>Service content here...</p>
</div>
aria-describedby
Provides additional description:
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-help" />
<span id="password-help">Must be at least 8 characters</span>
ARIA States
States change based on user interaction:
aria-expanded
Indicates if something is open/closed:
<button aria-expanded="false" onclick="toggleMenu()">
Menu
</button>
<button aria-expanded="true" onclick="toggleMenu()">
Menu
</button>
aria-hidden
Hides elements from screen readers:
<!-- Decorative icon - hide from screen readers -->
<span aria-hidden="true">🎨</span>
<!-- Hide content visually but keep accessible -->
<div aria-hidden="true" style="display: none;">
This is hidden from everyone
</div>
aria-live
Announces dynamic content changes:
<!-- Polite announcement (waits for user to finish) -->
<div aria-live="polite">New message received</div>
<!-- Assertive announcement (interrupts immediately) -->
<div aria-live="assertive">Error occurred!</div>
aria-disabled
Indicates element is disabled:
<button aria-disabled="true" disabled>Can't click me</button>
Complete Example
Here’s an accessible navigation and content example:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Accessible Page</title>
</head>
<body>
<!-- Header -->
<header role="banner">
<h1>My Accessible Site</h1>
<!-- Main Navigation -->
<nav aria-label="Main navigation">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<!-- Main Content -->
<main role="main">
<h2>Welcome</h2>
<!-- Accessible Form -->
<form>
<div>
<label for="name">Name (required)</label>
<input
type="text"
id="name"
name="name"
required
aria-required="true"
/>
</div>
<div>
<label for="email">Email (required)</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
aria-describedby="email-help"
/>
<span id="email-help">We'll never share your email</span>
</div>
<button type="submit">Submit</button>
</form>
<!-- Accessible Alert -->
<div role="alert" aria-live="assertive" id="form-status">
<!-- Status messages appear here -->
</div>
</main>
<!-- Footer -->
<footer role="contentinfo">
<nav aria-label="Footer navigation">
<ul>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/terms">Terms of Service</a></li>
</ul>
</nav>
<p>© 2026 My Site</p>
</footer>
<script>
// Show status message
function showStatus(message) {
const status = document.getElementById('form-status');
status.textContent = message;
}
</script>
</body>
</html>
Keyboard Navigation
Ensure all interactive elements are keyboard accessible:
<!-- Custom button with keyboard support -->
<div
role="button"
tabindex="0"
onclick="handleClick()"
onkeydown="if(event.key==='Enter'||event.key===' ')handleClick()"
>
Click me
</div>
<!-- Skip link for keyboard users -->
<a href="#main-content" class="skip-link">
Skip to main content
</a>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: white;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
</style>
ARIA Best Practices
- Use semantic HTML first — ARIA is a supplement
- Test with screen readers — NVDA, VoiceOver, JAWS
- Provide labels — All interactive elements need names
- Use live regions — For dynamic content updates
- Don’t hide focusable elements — Breaks keyboard navigation
Common Mistakes
- Using ARIA instead of HTML — Use
<button>notrole="button" - Missing labels — Inputs without labels hurt accessibility
- Using aria-hidden on focusable elements — Breaks keyboard nav
- Not testing with screen readers — Always test real usage
- Overusing aria-live — Too many announcements annoy users