HTML Accessibility: Not Optional, Not Hard
Practical accessibility guide: alt text, heading hierarchy, form labels, keyboard navigation, focus management, ARIA, color contrast, and screen reader testing.
Web accessibility isn't a feature you tack on at the end. It's not a nice-to-have. In the EU, US, Canada, UK, and a growing list of other places, it's a legal requirement. Lawsuits over inaccessible websites have been climbing every year. But even if the law didn't exist, making your site usable by everyone is just good engineering. You don't ship a building without wheelchair ramps, and you shouldn't ship a website that only works with a mouse.
The good news: most accessibility work is basic HTML done correctly. You don't need a specialized library or expensive audit tool to cover the fundamentals.
Alt Text for Images
Every needs an alt attribute. But "needs alt text" and "writes good alt text" are different things.
<!-- Bad: describes the element, not the content -->
<img src="team.jpg" alt="image">
<img src="team.jpg" alt="photo of team">
<!-- Good: describes what the image communicates -->
<img src="team.jpg" alt="Five team members standing in front of the office building">
<!-- Decorative image: empty alt, not missing alt -->
<img src="decorative-border.png" alt="">
The purpose of alt text is to convey the same information a sighted user gets from the image. If the image is a chart, describe the data point it's making. If it's a photo of your team, describe who's there. If it's purely decorative, use alt="" (empty string) so screen readers skip it entirely. Missing the alt attribute is different -- the screen reader will read the filename instead, which is useless.
Heading Hierarchy
Headings create an outline of your page. Screen reader users navigate by heading level the way sighted users scan visually. The rules are simple:
<!-- Correct: logical hierarchy -->
<h1>Python Tutorial</h1>
<h2>Variables</h2>
<h3>Strings</h3>
<h3>Numbers</h3>
<h2>Control Flow</h2>
<h3>If Statements</h3>
<h3>Loops</h3>
<!-- Wrong: skipping levels for styling -->
<h1>Python Tutorial</h1>
<h4>Variables</h4> <!-- jumped from h1 to h4! -->
One per page. Don't skip levels (h1 to h3 without h2). And never choose a heading level based on how it looks -- that's what CSS is for. An styled to look bigger than an is still an to a screen reader, and the resulting page outline makes no sense.
Form Labels
Every form input needs a programmatically associated label. "Programmatically associated" means the browser knows which label goes with which input -- not just that they're visually next to each other.
<!-- Method 1: explicit label with 'for' -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<!-- Method 2: wrapping (implicit association) -->
<label>
Email address
<input type="email" name="email">
</label>
<!-- Wrong: no association, screen reader doesn't know the label -->
<span>Email address</span>
<input type="email" name="email">
Also: placeholder text is not a label. It disappears when users start typing, it has poor contrast in most browsers, and screen readers handle it inconsistently. Use it for examples (placeholder="jane@example.com"), but always have a real label.
For groups of related inputs, use and :
<fieldset>
<legend>Shipping address</legend>
<label for="street">Street</label>
<input type="text" id="street" name="street">
<label for="city">City</label>
<input type="text" id="city" name="city">
</fieldset>
Keyboard Navigation
Not everyone uses a mouse. Some users rely on keyboards, switch devices, or other input methods. Your entire site should be operable with Tab, Shift+Tab, Enter, Space, and arrow keys.
Test it yourself: put your mouse in a drawer and try to use your site with only the keyboard.
Things that break keyboard navigation:
<!-- Bad: div with click handler, not focusable or activatable by keyboard -->
<div onclick="doSomething()">Click me</div>
<!-- Good: button is focusable and responds to Enter/Space -->
<button onclick="doSomething()">Click me</button>
<!-- If you MUST use a non-interactive element (you usually shouldn't): -->
<div role="button" tabindex="0" onclick="doSomething()"
onkeydown="if(event.key==='Enter')doSomething()">Click me</div>
The second example is significantly more code to replicate what gives you for free. Use the right element.
Focus Management
Focus should be visible. That default blue outline browsers add? Don't remove it without replacing it:
/ Bad: removes focus indicator entirely /
*:focus { outline: none; }
/ Good: custom focus style /
*:focus-visible {
outline: 2px solid #4A90D9;
outline-offset: 2px;
}
:focus-visible is the modern approach -- it shows the outline for keyboard navigation but hides it for mouse clicks, which is usually what designers want.
For single-page apps and dynamic content, manage focus when the view changes:
// After loading new content or navigating
document.getElementById("main-heading").focus();
If a modal opens, focus should move into the modal. When it closes, focus should return to the element that triggered it. If content gets removed from the page, focus shouldn't get lost in the void.
Color Contrast
WCAG requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. Light gray text on white backgrounds fails this consistently.
Don't convey information through color alone. If your form errors are red text, also add an icon or prefix text:
<!-- Bad: relies only on color -->
<span style="color: red">Invalid email</span>
<!-- Good: icon + text + color -->
<span style="color: #D32F2F">
⚠ Error: Invalid email address
</span>
A user who can't distinguish red from surrounding text still gets the message.
Skip Links
For keyboard users, tabbing through your entire navigation on every page is tedious. A skip link lets them jump to the content:
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>
<!-- 20 navigation links -->
</nav>
<main id="main-content">
<!-- page content -->
</main>
</body>
.skip-link {
position: absolute;
left: -9999px;
z-index: 999;
}
.skip-link:focus {
position: fixed;
top: 10px;
left: 10px;
background: #000;
color: #fff;
padding: 8px 16px;
}
Hidden by default, visible on focus. The first thing a keyboard user hits is a link straight to the content.
ARIA: When Semantic HTML Isn't Enough
ARIA (Accessible Rich Internet Applications) fills gaps where HTML elements don't exist for your use case. The key principle: use semantic HTML first, ARIA only when needed.
Common ARIA patterns:
<!-- Live regions: announce dynamic content changes -->
<div aria-live="polite">
3 items in your cart
</div>
<!-- Current page in navigation -->
<nav>
<a href="/" aria-current="page">Home</a>
<a href="/about">About</a>
</nav>
<!-- Expanded/collapsed state -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>...</ul>
<!-- Required fields -->
<input type="email" aria-required="true">
<!-- Error messages -->
<input type="email" aria-invalid="true" aria-describedby="email-error">
<span id="email-error">Please enter a valid email address</span>
aria-live="polite" is particularly important for SPAs -- when content updates without a page reload, screen readers won't notice unless you tell them. The "polite" value means it waits until the user is idle before announcing.
Testing
You don't need to be an accessibility expert to test the basics:
- Keyboard test: Navigate your entire site with just Tab and Enter. Can you reach everything? Can you see where focus is?
- Heading check: Install the HeadingsMap browser extension. Does your outline make sense?
- Screen reader: Try VoiceOver (Mac), NVDA (Windows, free), or TalkBack (Android). Even 10 minutes of screen reader testing reveals problems you'd never find visually.
- Automated scan: Run Lighthouse or axe DevTools. They catch about 30-40% of issues automatically -- missing alt text, low contrast, missing labels.
- Zoom test: Zoom to 200%. Does the layout still work? Is text readable?
Accessibility is a skill you build with practice, just like any other part of web development. If you want to work through exercises focused on building accessible, well-structured web pages, CodeUp has HTML and CSS challenges that emphasize doing things correctly from the ground up.