Accessibility (often shortened to a11y, because there are 11 letters between the "a" and the "y") means building websites that everyone can use. That includes people who are blind and use screen readers, people who can't use a mouse, people with low vision or colour blindness, and anyone with a broken arm or bright sunlight on their screen.
The good news is that most accessibility comes from writing good, semantic HTML. If you've followed the earlier lessons, you already know much of it. This lesson pulls those habits together and adds a few more, so you can check any page you build.
How people browse the web differently
- Screen readers such as VoiceOver, NVDA and JAWS read the page aloud and let people jump between headings, links, landmarks and form fields.
- Keyboard users press Tab to move between links and buttons, and Enter or Space to use them.
- Zoom users magnify text to 200% or more, so layouts must still work at large sizes.
- Colour-blind users can't rely on a difference between red and green alone.
A quick way to experience this: put your mouse away and try to use one of your own pages with only the keyboard.
Six habits that make HTML accessible
- Use the right element. Real
<button>,<a>,<nav>and<main>elements come with keyboard and screen-reader support built in. - Add alt text to images. Describe meaningful images, and use
alt=""for decoration. - Label every form field, connecting the label with
forandid. - Keep a logical heading outline: one
<h1>, then<h2>and<h3>in order. - Write descriptive link text instead of "click here".
- Set the page language with
<html lang="en">.
Keyboard focus and colour contrast
When someone tabs through a page, the browser draws an outline around the focused element. That outline is how keyboard users know where they are, so never remove it without providing a clear replacement style.
Text also needs enough contrast against its background. Light grey text on a white background may look elegant, but many people can't read it. The WCAG guidelines recommend a contrast ratio of at least 4.5 to 1 for normal text, and browser developer tools can check this for you.
ARIA attributes: the last resort
ARIA attributes add extra information for assistive technology:
aria-label="Close"gives a name to a button that only shows an icon.aria-expanded="true"tells users a menu is open.aria-live="polite"announces content that changes, such as "Item added to basket".aria-hidden="true"hides decorative things, such as icons, from screen readers.
The first rule of ARIA is: if a native HTML element does the job, use that instead. A <button> beats <div role="button"> every time, because the native element already handles focus, keyboard presses and announcements.
HTML Accessibility Syntax
Common accessible patterns
<img src="chart.png" alt="Sales rose 40% in 2026">
<label for="q">Search</label> <input id="q" type="search">
<button type="button" aria-label="Close menu">✕</button>
<a class="skip-link" href="#main">Skip to content</a>HTML Accessibility Summary
- Accessibility means people can use your site regardless of disability or device.
- Semantic HTML provides most accessibility features automatically.
- Use alt text, form labels, a clear heading outline and descriptive links.
- Keep the focus outline visible and use strong colour contrast.
- Reach for ARIA only when no native HTML element does the job.
HTML Accessibility Examples
Each example runs in the page. Edit the code and press Run to see what changes.
An icon button with a name
The ✕ means nothing to a screen reader. aria-label gives the button a name. Press Tab in the result to see the keyboard focus ring.
<div role="alertdialog" aria-labelledby="t">
<h3 id="t">Cookies</h3>
<p>We only use cookies to remember your theme.</p>
<button type="button">Accept</button>
<button type="button" aria-label="Close notice">✕</button>
</div>Line-by-line explanation
- Line 1
aria-labelledbypoints to the heading, so the dialog is announced as "Cookies". - Line 4A normal button. Its text is its name.
- Line 5An icon-only button.
aria-labelis what a screen reader says.
Live announcements
Click the button. Screen readers announce the text inside aria-live whenever it changes, without moving the user's focus.
<button type="button" onclick="document.getElementById('cart').textContent = 'Added! 2 items in your basket.'">
Add to basket
</button>
<p id="cart" aria-live="polite">Your basket is empty.</p>Line-by-line explanation
- Lines 1 to 3A real button, so it works with mouse, keyboard and touch.
- Line 4
aria-live="polite"waits until the screen reader finishes speaking, then announces the new text.
A skip link
Press Tab once in the result: a "Skip to content" link appears. Keyboard users can jump past long menus. This site has one too, so try it here.
<a href="#main" class="skip">Skip to content</a>
<nav>Home · Shop · Blog · About · Contact · Help</nav>
<main id="main" tabindex="-1">
<h1>Welcome</h1>
</main>
<style>
.skip { position: absolute; left: -999px; }
.skip:focus { left: 8px; top: 8px; background: #ffd23f; padding: 6px 10px; }
</style>Line-by-line explanation
- Line 1The skip link points at
#main. - Lines 8 to 9It's hidden off-screen until it receives keyboard focus.
Common HTML Accessibility Mistakes
Clickable divs
<div class="btn" onclick="save()">Save</div><button type="button" class="btn" onclick="save()">Save</button>The div can't be reached with Tab, doesn't respond to Enter, and isn't announced as a button.
Colour as the only signal
<p style="color: red">Email</p>
<input type="email">
<!-- red = error? --><label for="em">Email</label>
<input id="em" type="email" aria-describedby="em-err">
<p id="em-err">⚠ Please enter a valid email address.</p>Colour-blind users can't see "red means error". Add text or an icon, and connect the message to the field.
Removing the focus outline
<style> *:focus { outline: none; } </style><style> *:focus-visible { outline: 3px solid #17a865; outline-offset: 2px; } </style>Keyboard users rely on the outline to know where they are. Style it, but never delete it.
HTML Accessibility in Real Projects
Accessibility helps everyone
Captions help people in noisy cafés. Big buttons help people on bumpy buses. Good contrast helps everyone outdoors. Clear headings help everyone skim. And because search engines read pages a bit like screen readers do, accessible pages often rank better too.
HTML Accessibility Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Give the icon-only search button an accessible name.
<button type="button" ="Search">🔍</button>
aria-label provides a name when there's no visible text.What's the first rule of ARIA?
Which key do keyboard users press to move to the next link or button?
Make this mini form accessible: connect the label, give the image alt text, and turn the clickable div into a real submit button.
<img src="https://picsum.photos/id/292/120/80" width="120" height="80">
<form>
<label>Your email</label>
<input type="email" id="news-email" name="email">
<div onclick="this.closest('form').requestSubmit()">Subscribe</div>
</form>- Image has alt text
- Label is connected to the input
- There's a real submit button
- No clickable div left
HTML Accessibility Quiz
4 questions to check what you learned.
What does "a11y" mean?
- Ally, a JavaScript library
- An HTML attribute
- Version 11 of HTML
- Accessibility (a + 11 letters + y)
Which attribute announces changing content to screen readers?
- aria-hidden
- aria-change
- aria-live
- role="alert-me"
What is WCAG?
- A CSS framework
- The Web Content Accessibility Guidelines
- A screen reader
- A Google ranking tool
Why is
outline: noneon focus a problem?- Keyboard users can no longer see which element is focused
- It slows the page down
- It breaks links
- It's invalid CSS
HTML Accessibility Challenge
Fix the hamburger menu button
This menu button uses only an icon. Make it accessible:
- Change the
<span>into a<button type="button"> - Give it
aria-label="Open menu" - Add
aria-expanded="false"
<span class="menu-btn" onclick="this.textContent = '✕'">☰</span>- It's a real button
- It has aria-label="Open menu"
- It has aria-expanded="false"
- No span left