Expert · Fast, accessible, findable and safe: quality for real sites · topic 47 of 64 · 35 min
Accessibility in depth: keyboard, ARIA and WCAG
Explanation
WCAG 2.2 in simple words
In beginner-3 you met alt text, labels, real buttons, lang and colour contrast. Now we go deeper. WCAG (Web Content Accessibility Guidelines) comes from the W3C, and version 2.2 (October 2023) is the current one. Each rule, called a success criterion, has a level: A is the bare minimum, AA is the normal target that laws and companies ask for, and AAA is the strictest. Its four principles spell POUR: Perceivable, Operable, Understandable and Robust.
| Rule (level) | In simple words |
|---|---|
| Contrast (AA) | Normal text needs a contrast ratio of at least 4.5:1 with its background. Large text (about 24px, or about 19px bold) and the borders and icons of controls need 3:1. |
| Keyboard (A) | Everything you can do with a mouse also works with the keyboard alone. |
| Focus visible (AA) | The element that has keyboard focus is clearly marked. |
| Focus not obscured (AA, new in 2.2) | A sticky header or cookie banner must not completely hide the focused element. |
| Target size (AA, new in 2.2) | Click and tap targets are at least 24 by 24 CSS pixels, or have enough space around them. |
| Name, role, value (A) | Every control tells assistive technology what it is, what it is called and what state it is in. |
Keyboard and visible focus
Keyboard users press Tab to move forward, Shift+Tab to go back, Enter to follow links and press buttons, Space to tick boxes and press buttons, arrow keys inside radio groups and menus, and Escape to close pop-ups. Links, buttons and form fields can get focus on their own. Never remove the focus outline without giving a better one.
/* Bad: hides focus for everyone */
/* button:focus { outline: none; } */
/* Good: a clear ring when the user is using the keyboard */
:focus-visible {
outline: 3px solid #1a56db;
outline-offset: 2px;
}<body>
<a class="skip-link" href="#main">Skip to main content</a>
<header>... logo and 30 menu links ...</header>
<main id="main">
<h1>Train timetable</h1>
</main>
</body>- tabindex="0" adds an element to the normal Tab order. Use it only for a custom widget; a real <button> needs nothing.
- tabindex="-1" lets JavaScript focus an element with element.focus() but keeps it out of the Tab order, for example an error summary you move focus to.
- Never use a positive tabindex such as tabindex="3". It forces a custom order that breaks the natural one and confuses everyone. Fix the order in the HTML instead.
Landmarks and headings
Screen reader users move around a page by landmarks (header, nav, main, aside, footer) and by headings, like using the contents page of a book. Use one h1 for the page topic, then h2 for sections and h3 inside them, without skipping levels just to get a smaller font. If you have two nav elements, name them: <nav aria-label="Main"> and <nav aria-label="Footer">.
ARIA: only to fill gaps
ARIA (Accessible Rich Internet Applications) is a set of attributes that add roles, names and states for assistive technology. It changes nothing for mouse users and adds no keyboard behaviour. The first rule of ARIA: if a native HTML element does the job, use it. A wrong role or a state that is never updated misleads users more than no ARIA at all, which is why people say "no ARIA is better than bad ARIA".
<!-- An icon-only button needs a name -->
<button type="button" aria-label="Close">×</button>
<!-- A menu toggle tells whether it is open (JavaScript updates it) -->
<button type="button" aria-expanded="false" aria-controls="site-menu">Menu</button>
<nav id="site-menu" hidden>...</nav>
<!-- Text that appears later is read out without moving focus -->
<p id="cart-status" aria-live="polite"></p>The accessible name is what a screen reader calls a control. It comes from the text inside a button or link, a connected <label>, the alt of an image, or aria-label and aria-labelledby. "Click here" and "Read more" are poor names when a user lists all links; "Read more about NEET results" is clear. For messages that appear later, such as "Saved" or "3 errors in the form", use a live region: aria-live="polite" (or role="status") waits for a pause, while role="alert" interrupts and is only for urgent errors.
<button type="button" id="open-confirm">Cancel booking</button>
<dialog id="confirm-dialog" aria-labelledby="confirm-title">
<h2 id="confirm-title">Cancel this booking?</h2>
<form method="dialog">
<button value="no">Keep it</button>
<button value="yes">Yes, cancel</button>
</form>
</dialog>
<script>
const dialog = document.querySelector('#confirm-dialog');
document.querySelector('#open-confirm')
.addEventListener('click', () => dialog.showModal());
</script>How to test
- Put the mouse away and Tab through the whole page. Can you always see the focus? Can you reach and use everything? Do pop-ups close with Escape?
- Use a screen reader: NVDA (free) or Narrator on Windows, VoiceOver on Mac and iPhone, TalkBack on Android. Listen to a form and a menu.
- Zoom to 200% and check that nothing is cut off. Turn on "reduce motion" on your phone and check big animations stop (you met prefers-reduced-motion in intermediate-1).
- Run the Lighthouse Accessibility audit and the axe DevTools browser extension.
Watch the lesson
Accessibility in depth: keyboard, ARIA and WCAG · 1/8
Accessibility means everyone can use your site: screen reader users, keyboard users, people with low vision or shaky hands.
Prefer a video by a teacher? Find free videos on “web accessibility keyboard focus aria screen reader tutorial wcag 2.2” on YouTube ↗
In real life: Exam results for every student
On result day a state board site gets lakhs of visits. A blind student uses NVDA, and another student with a hand injury uses only the keyboard. The developers add a skip link, connect a label to the roll number field, move focus to the result heading when it loads, announce "Result loaded" in a live region, and make sure the "Download marksheet" button is a real button with a visible focus ring. Both students get their results on their own, without asking anyone for help.
Practice (0/3 done)
Make this tuition class page keyboard and screen reader friendly: (1) add a skip link as the very first focusable element in <body>: <a href="#main">Skip to main content</a>; (2) remove every positive tabindex from the menu links; (3) give the menu button (id="menu-btn") aria-expanded="false" and aria-controls="site-menu"; (4) make the message paragraph (id="form-message") a polite live region with aria-live="polite" (or role="status").
Write contrastRatio(colour1, colour2) for two colours written as 6-digit hex strings like "#767676". Steps: (1) for each colour, read red, green and blue (0 to 255) and divide each by 255; (2) turn each channel c into a linear value: if c <= 0.04045 use c / 12.92, else ((c + 0.055) / 1.055) ** 2.4; (3) luminance L = 0.2126 * R + 0.7152 * G + 0.0722 * B; (4) ratio = (lighter L + 0.05) / (darker L + 0.05). Return the ratio rounded to 2 decimals with Math.round(ratio * 100) / 100. Examples: contrastRatio("#000000", "#ffffff") is 21; contrastRatio("#767676", "#ffffff") is 4.54, which just passes the 4.5:1 rule.
Test one real page (your portfolio, a college site or a booking form) without a mouse and with a screen reader. Use NVDA (free) or Narrator on Windows, VoiceOver on Mac or iPhone, or TalkBack on Android. Write a short report with problems and fixes.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.