HTML Accessibility Basics
Write HTML that works for everyone — alt text, ARIA, keyboard navigation, and semantic structure
Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.
UpgradeKnowledge Debt detected
You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.
Explanation
Accessibility (a11y) means your site works for people using screen readers, keyboard-only navigation, or low-vision tools. Most of it is good HTML — not extra work.
The first rule of ARIA: don't use it if native HTML does the job. ``html <!-- Bad: div pretending to be a button --> <div onclick="submit()">Submit</div> <!-- Good: actual button — keyboard, focus, ARIA, roles included for free --> <button type="submit">Submit</button>
Alt text — three cases:
html <!-- 1. Informational: describe what the image shows --> <img src="chart.png" alt="Bar chart showing 40% increase in Q2 revenue" /> <!-- 2. Decorative: empty alt — screen readers skip it --> <img src="divider.svg" alt="" /> <!-- 3. Functional (linked or button icon): describe the action --> <a href="/home"><img src="logo.png" alt="AtomLearn — go to home page" /></a>
ARIA — use sparingly, only when HTML has no equivalent:
html <!-- aria-label: names an element when visible text isn't enough --> <button aria-label="Close dialog">✕</button> <!-- aria-labelledby: points to another element's text as the label --> <section aria-labelledby="section-title"> <h2 id="section-title">Recent Atoms</h2> ... </section> <!-- aria-live: announces dynamic content changes to screen readers --> <div aria-live="polite" role="status">Form submitted successfully</div> <!-- aria-hidden: removes element from the accessibility tree --> <span aria-hidden="true">★★★★☆</span> <span class="sr-only">Rating: 4 out of 5 stars</span>
Keyboard navigation:
- Every interactive element (links, buttons, inputs) must be focusable and usable with Enter/Space
- Use
tabindex="0"to make a non-interactive element focusable - Never use
tabindexvalues > 0 — it breaks the natural tab order tabindex="-1"makes an element focusable via JS only (not in tab order)
Visible focus indicator:
css /* Never do this — it makes keyboard navigation impossible */ :focus { outline: none; } /* Do this instead — custom focus ring that's still visible */ :focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; }
Screen-reader-only text:
css .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; }
Examples
Accessible icon button
`aria-hidden="true"` on decorative icons removes them from the a11y tree; the visible text or `aria-label` provides the accessible name
<!-- Bad: no accessible name, not keyboard accessible -->
<div onclick="handleClose()">✕</div>
<!-- Good: button with aria-label for the icon's meaning -->
<button type="button" aria-label="Close dialog" onclick="handleClose()">
✕
</button>
<!-- Good: icon + screen-reader-only text -->
<button type="button">
<svg aria-hidden="true" focusable="false"><!-- icon svg --></svg>
<span class="sr-only">Close dialog</span>
</button>Accessible form error feedback
`aria-describedby` links an error message to its input; `role="alert"` triggers immediate announcement; `aria-invalid="true"` signals the error state
<form>
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-error"
aria-invalid="true"
/>
<!-- Error message linked to input via aria-describedby -->
<p id="email-error" role="alert">
Please enter a valid email address.
</p>
</form>
<!-- Loading state announced to screen readers -->
<button type="submit" aria-busy="true" disabled>
Saving...
</button>How well did you understand this?