Lesson 5

Accessibility

Home › Courses › HTML › Lesson 5
🟠 Lesson 5 · 25 minutes

Accessibility.
HTML that works for everyone.

About 15% of the world has some form of disability. Every developer who ignores accessibility is building a site that excludes one in seven people. The good news: accessible HTML is usually simpler, not more complex. The patterns you've already learned — semantic elements, labels, alt text, native buttons — are the majority of the work. This lesson fills in the rest.

~25 minutes Beginner → Intermediate HTML5 · WCAG 2.1
HTML path ← Lesson 4 Why it matters Alt text Keyboard ARIA Test it Challenge

Accessibility is mostly already-done if you've followed this track: semantic HTML gives structure, labels give form fields meaning, alt text describes images, and native <button> handles keyboard support automatically. The rest is not breaking it — don't use div instead of button, don't skip focus styles, don't use color alone. When semantic HTML can't express something, use ARIA — but only as a fallback. Test with keyboard Tab and a screen reader, and you'll catch 90% of problems.

Before this lesson: complete Lesson 4. Accessibility builds on everything you've learned so far.

🌍 Why accessibility matters

Three reasons. All of them matter more than the extra ten minutes of work.

1. It's the right thing

1.3 billion people — 15% of the world — live with a disability. Building a site that excludes them is a choice, not an inevitability. Accessibility is about dignity, not charity. The web was invented as a universal space; accessibility is how that promise stays true.

2. It's often legally required

WCAG 2.1 AA is legally mandated in many jurisdictions. The ADA in the US, the Equality Act in the UK, the European Accessibility Act (2025), and many others. Lawsuits against inaccessible websites are common — thousands per year. Accessibility isn't a nice-to-have; it's a compliance requirement.

3. It improves everything

Accessible sites are better for everyone: semantic HTML helps SEO, alt text helps users with slow connections, keyboard support helps power users, higher contrast helps people with tired eyes. The best accessibility features are the ones everyone uses without realizing it. It's not a burden — it's a quality bar.

The most common objection — "it takes too much time" — is false. If you use semantic HTML from the start, accessibility is nearly free. The extra cost comes from retrofitting accessibility into HTML that was built without it. That's why we teach it in this order: semantic first, then forms, then accessibility. By the time you finish this lesson, your HTML is already 70% accessible.

🖼️ Alt text — the image accessibility rule

Three cases. Every image fits one of them.

HTML
<!-- 1. MEANINGFUL image — describe the content -->
<img src="chart.png" alt="Q3 revenue grew 20% over Q2">

<!-- 2. FUNCTIONAL image — describe the destination -->
<a href="/">
<img src="logo.svg" alt="Home">
</a>

<!-- 3. DECORATIVE image — empty alt, but present -->
<img src="divider.png" alt="">

<!-- WRONG: missing alt (screen reader announces filename) -->
<img src="chart.png">

<!-- WRONG: redundant "image of" phrasing -->
<img src="chart.png" alt="Image of a chart showing Q3 revenue">
<!-- Screen readers already say "image", so "image of" is duplicated -->

<!-- WRONG: filename as alt text -->
<img src="IMG_4832.jpg" alt="IMG_4832.jpg">

<!-- WRONG: "click here" as link alt -->
<a href="/products"><img src="shop.png" alt="Click here"></a>
<!-- Fix: alt="View products" -->

Missing alt vs empty alt — they're different.

<img src="x.png"> — missing alt. Screen reader announces the filename: "x dot p n g, image." Useless.

<img src="x.png" alt=""> — empty alt. Screen reader skips it entirely. Correct for decorative images.

Always include the alt attribute, even if empty. Missing is a bug; empty is a decision.

The "swap test" for alt text. Imagine you replaced the image with its alt text. Would the page still make sense? If yes, the alt is good. If it now says "IMG_4823" or "photo", it needs work. If the alt is empty but the image conveyed important information, the page now has a gap. That gap is what blind users experience.

⌨️ Keyboard navigation — the invisible feature

Every interactive element must be reachable and operable without a mouse.

HTML — keyboard-accessible by default
<!-- These are ALL keyboard-accessible out of the box -->
<button>Click me</button> <!-- Tab + Enter/Space -->
<a href="/about">About</a> <!-- Tab + Enter -->
<input type="text"> <!-- Tab, type -->
<input type="checkbox"> <!-- Tab + Space to toggle -->
<select>...</select> <!-- Tab + arrow keys -->
<textarea></textarea> <!-- Tab, type -->

<!-- These are NOT keyboard-accessible -->
<div onclick="doSomething()">Click me</div>
<span onclick="submit()">Submit</span>
<a>Link without href</a>

<!-- THE FIX: use the native element -->
<button onclick="doSomething()">Click me</button>

<!-- If you must use a div, add keyboard support manually -->
<div role="button" tabindex="0" onclick="doSomething()"
onkeydown="if(event.key==='Enter'||event.key===' ') doSomething()">
Click me
</div>
<!-- But WHY? Just use <button> -->

Tab moves forward

Tab cycles through focusable elements in DOM order. Shift+Tab reverses. Only native interactive elements and elements with tabindex="0" are in the tab order.

Enter and Space activate

Enter activates links and buttons. Space activates buttons and toggles checkboxes. These behaviors are automatic on native elements — free keyboard support.

Visible focus is required

The focus indicator (usually an outline) tells users where they are. Never do outline: none without providing an alternative. If you remove it, users can't see what they're about to activate.

tabindex="-1"

Removes an element from the tab order but lets JavaScript focus it. Useful for focus management — moving focus to a modal when it opens. Rarely needed for content elements.

Never use tabindex greater than zero. tabindex="1", tabindex="2" reorder the tab sequence and break the natural flow. Users expect to tab through content in the order it appears — jumping around is confusing. If the tab order is wrong, fix the DOM order, not the tabindex.

Do the Tab test on your own site. Open the page, close your eyes, and try to navigate with only the keyboard. Can you reach every button? Do you know where focus is? If you get lost, your users will too. This one test catches more accessibility problems than any automated tool.

🎯 ARIA — the fallback, not the first choice

ARIA adds accessibility info HTML can't express. Use it only when needed.

The first rule of ARIA is: don't use ARIA if you can use native HTML instead. A <button> with native keyboard support beats a <div role="button" tabindex="0"> every time. ARIA is for the cases where native HTML genuinely can't express what you need — live regions, complex widgets, screen-reader-only labels.

HTML — ARIA attributes you'll actually use
<!-- aria-label — text for elements with no visible text -->
<button aria-label="Close dialog">✕</button>

<!-- aria-labelledby — point to visible text as the label -->
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Delete account</h2>
<p>Are you sure?</p>
</div>

<!-- aria-describedby — point to additional description -->
<input type="password" aria-describedby="pw-hint">
<p id="pw-hint">Password must be at least 8 characters.</p>

<!-- aria-live — announce changes to screen readers -->
<div aria-live="polite">
<!-- When content inside changes, screen readers announce it -->
</div>

<!-- aria-live values -->
<!-- "polite" — wait for user to finish current task -->
<!-- "assertive" — interrupt immediately (use only for errors) -->

<!-- aria-hidden — hide from screen readers (decorative content) -->
<span aria-hidden="true">🎉</span>

<!-- aria-expanded — for collapsible elements -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>...</ul>

<!-- Screen-reader-only text -->
<span class="visually-hidden">Loading</span>
<!-- CSS: .visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); } -->

aria-label

Text label for elements with no visible text — usually icon buttons. <button aria-label="Search">🔍</button>. The emoji alone tells screen reader users nothing.

aria-labelledby

Points to the id of visible text that serves as the label. Preferred over aria-label when visible text exists — the accessible name matches what users see.

aria-live

Announces changes to screen readers as they happen. Use for status messages, form validation, chat notifications. aria-live="polite" waits; "assertive" interrupts.

aria-hidden

Hides from screen readers but not visually. Use for decorative emoji, duplicate icons, and content that's redundant with nearby text. Never on interactive elements.

The #1 ARIA mistake: adding ARIA to fix problems that semantic HTML already solves. A <div role="button"> doesn't handle Enter/Space — you have to add onkeydown handlers. A native <button> does it for free. Use semantic HTML first, ARIA only when native elements genuinely can't do the job.

🎨 Color and contrast

The accessibility rule that visual designers fight against — and shouldn't.

Contrast ratios

WCAG AA requires 4.5:1 for normal text, 3:1 for large text (18px+ bold or 24px+ regular). AAA requires 7:1. Check any color pair at webaim.org/resources/contrastchecker.

Don't use color alone

Red for error, green for success — 8% of men are color-blind. Add an icon (✓ or ✕), a text label ("Error:"), or a shape difference. Color-blind users see a different color; sighted color-blind users still get the message.

Links vs text

Links that are the same color as body text (differing only by color) fail WCAG. Add underline, or a different font-weight, or a background. Users with low vision may not distinguish similar colors.

Focus indicators

Focus rings need at least 3:1 contrast with adjacent colors. When you remove outline: none, replace it with a custom :focus-visible style that meets contrast requirements.

Test with the "grayscale test". In Chrome DevTools → Rendering → Emulate vision deficiencies → Achromatopsia (grayscale). If your UI is confusing in grayscale, it fails for color-blind users. The best accessibility check for color: does everything still make sense without color? If yes, you're using color as enhancement. If no, you're relying on it — a WCAG violation.

🔬 Test it — three practical checks

Automated tools catch 30%. These three tests catch the other 70%.

1

The Tab test

Open your page, close your eyes, and Tab through it. Every interactive element should get focus, in order, with a visible focus indicator. If you can't reach something with Tab, it's inaccessible to keyboard users — which includes many screen reader users, power users, and users with motor impairments.

2

The screen reader test

Turn on VoiceOver (macOS, Cmd+F5) or NVDA (Windows, free download). Navigate your page for 5 minutes. Listen to what it announces. Does the structure make sense? Are images described? Do form fields have labels? Even 5 minutes reveals most accessibility problems.

3

The automated audit

Run Lighthouse accessibility in Chrome DevTools (Lighthouse tab → Generate report). Install axe DevTools browser extension for a more thorough scan. Run WAVE at wave.webaim.org. Each catches different classes of problems. Automated tools find ~30%; manual testing finds the rest.

Do these three tests before every launch. 10 minutes total. They catch more accessibility bugs than months of writing correct-looking code without testing. The only way to know your site is accessible is to use it the way your users do.

🏆 Challenge

Three tasks. Each one tests a different accessibility skill.

Task 1 — Fix the broken page

Here's a page with five accessibility problems. Identify and fix each one:

1. <div onclick="submit()">Submit</div> — not keyboard-accessible
2. <img src="chart.png"> — missing alt text
3. <input type="email" placeholder="Email"> — no label
4. body { color: #666; background: #777; } — fails contrast
5. * { outline: none; } — no focus indicator

Rewrite each. For the color issue, suggest a specific fix. For the div, explain why button is better.

Task 2 — Add ARIA where needed

Take the page you built in the previous challenge (or any page you've built). Add ARIA attributes where appropriate:

1. A close button (✕ icon) needs aria-label="Close"
2. A menu button needs aria-expanded and aria-controls
3. A live search results area needs aria-live="polite"
4. A password field needs aria-describedby pointing to the requirements text
5. An emoji decoration needs aria-hidden="true"

Remember: only add ARIA where semantic HTML can't already handle it. Don't add ARIA to buttons, links, or form fields that already work.

Task 3 — Full audit

Pick any real website you use daily (GitHub, Reddit, your bank, whatever). Open Chrome DevTools and:

1. Run the Lighthouse accessibility audit — what score does it get?
2. Install axe DevTools, run the audit — how many issues?
3. Tab through the page — is everything reachable?
4. Check one image — does it have meaningful alt text?
5. Check one form — does it have proper labels?

Then come back to your own site and do the same audit. Compare. Your score should be higher than theirs (you've been writing semantic HTML for five lessons). If it isn't, you now know what to fix.

⚠️ The 8 mistakes that cause 90% of failures

Every one is preventable. Most are caught by automated tools.

1. Missing alt text on images

Every <img> needs alt. Meaningful images describe content. Functional images describe destination. Decorative images use alt="". Never omit the attribute entirely.

2. div with onclick instead of button

<div onclick> has no keyboard support, no focus ring, no role. Fix: use <button>. It's one word different and comes with everything.

3. No visible focus indicator

outline: none without replacement means keyboard users can't see where they are. Fix: replace with a custom :focus-visible style. Or just leave the default outline alone.

4. Low color contrast

Gray-on-white text, light-on-light buttons, subtle borders. All fail WCAG. Fix: check every color pair at webaim.org/resources/contrastchecker. Aim for 4.5:1 minimum.

5. Missing form labels

Placeholder as label is not a label. Fix: every input needs a <label for> or a wrapping <label>. Screen readers announce the label.

6. Color alone conveying meaning

Red for error, green for success, only color. Fails for color-blind users. Fix: add text, an icon, or a shape difference. "Error: Invalid email" not just a red border.

7. Skipping heading levels

h1 → h4 because h4 is the right size. Fix: h1 → h2 → h3, in order. Screen readers use headings to build a document outline — skipped levels break navigation.

8. Missing lang attribute

<html> without lang="en" — screen readers guess the language wrong, pronouncing everything with the wrong phonetics. Fix: <html lang="en">. One attribute.

🎓 HTML track complete

Five lessons. One real skill. Here's what you can do now.

You can build real pages

Semantic structure, forms, images, links, lists — every element a real page needs. Nothing in the HTML spec is unfamiliar anymore.

You can write accessible HTML

Alt text, labels, keyboard support, ARIA, contrast — the patterns most developers learn years into their careers. You have them from day one.

You can pass accessibility audits

Lighthouse, axe, WAVE — the automated tools will score high because you're already following the rules. The manual tests (Tab, screen reader) are habits you can now practice.

You're ready for CSS and JavaScript

HTML is the foundation. CSS styles it. JavaScript makes it interactive. You've built the foundation correctly — everything you learn next builds on solid ground.

What to do before moving to CSS. Build one complete page from scratch: a blog post, a product page, a personal homepage. Make it semantic. Make it accessible. Run an audit on it. Fix everything the audit flags. Ship it. That one page will teach you more than the next ten tutorials — because you'll have to solve real problems, not follow curated examples.

➡️ What's next

You've finished the HTML track. Three directions from here.

❓ Frequently asked questions

What is HTML accessibility?

HTML accessibility means writing HTML that works for everyone — users who are blind, deaf, color-blind, motor-impaired, or using keyboard-only navigation. It's not a separate feature; it's a quality standard. About 15% of the world has some form of disability, and accessible HTML also helps SEO, mobile users, and users with slow connections.

What is alt text and why does it matter?

Alt text is the description of an image for users who can't see it. Screen readers announce it. For meaningful images, describe the content. For functional images (logos that link home), describe the destination. For decorative images, use alt='' (empty) — never omit the attribute entirely.

What is ARIA and when should I use it?

ARIA is a set of attributes that add accessibility information to HTML when semantic HTML can't express it. Rule: use semantic HTML first. ARIA is a fallback, not a first choice. A button element beats a div with role='button' — the native button has keyboard support and focus management built in.

What is keyboard navigation and why does it matter?

Keyboard navigation means users can reach and operate every interactive element using only the keyboard — no mouse. Many users can't use a mouse: motor impairments, repetitive strain injuries, power users, screen reader users. Native HTML elements are keyboard-accessible by default. Divs with onclick are not.

What is WCAG and what levels does it have?

WCAG is the international standard for web accessibility. Three levels: A (minimum), AA (standard, legally required in many jurisdictions), AAA (highest, rarely achieved). Organized around four principles: Perceivable, Operable, Understandable, Robust (POUR). Most sites target WCAG 2.1 AA.

What is the difference between aria-label and aria-labelledby?

aria-label gives an element a text label directly. aria-labelledby references the id of another element whose text becomes the label. Use aria-label when there's no visible text to point at (icon buttons). Use aria-labelledby when there's visible text that should serve as the label.

How do I test HTML accessibility?

Three practical tests: (1) Tab through the page — every interactive element should be reachable and have a visible focus indicator. (2) Use a screen reader — VoiceOver on macOS, NVDA on Windows. (3) Run automated tools — axe DevTools, Lighthouse, WAVE. Automated tools catch about 30% of issues; manual testing catches the rest.

What are the most common accessibility mistakes?

Missing alt text, div with onclick instead of button, no visible focus indicator, low color contrast, missing form labels, using color alone to convey meaning, skipping heading levels, and missing lang attribute. Every one is preventable and most are caught by automated tools.

🌐 External resources

← Back to HTML track
You're in the HTML track · Lesson 5 · Track complete