Skip to content

Expert · Fast, accessible, findable and safe: quality for real sites · topic 47 of 64 · 35 min

Accessibility in depth: keyboard, ARIA and WCAG

Explanation

Accessibility means people with disabilities can use your site: blind users with screen readers, people who use only a keyboard, people with low vision or shaky hands. WCAG 2.2 is the global checklist, and level AA is the usual target. In practice: everything works with the keyboard and shows visible focus, the page has landmarks and good headings, native HTML comes first, and ARIA only fills the gaps.

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.

css
/* 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;
}
:focus-visible shows the ring when it helps (keyboard use), not on every mouse click
html
<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>
A skip link, first in the page, lets keyboard users jump past the menu. CSS can hide it off-screen until it gets focus.
  • 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".

Think of it like this: ARIA is like a sticker on a box. A real pressure cooker already whistles; it needs no sticker. If you stick a "pressure cooker" label on a plain steel dabba, people will wait for a whistle that never comes. Native elements are the real cooker; ARIA is only the sticker, so make sure the box really behaves the way the sticker says.
html
<!-- An icon-only button needs a name -->
<button type="button" aria-label="Close">&times;</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>
aria-label names a control, aria-expanded gives its state, aria-live announces new text

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.

html
<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>
showModal() moves focus into the dialog, makes the page behind inert and closes on Escape; a form with method="dialog" closes it too

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.
Careful: Automated tools find only part of the problems. They cannot tell whether your alt text makes sense, whether the focus order is logical, or whether a screen reader user can actually finish the form. Always test by hand with the keyboard and a screen reader too.
Tip: In India, the Rights of Persons with Disabilities (RPwD) Act, 2016 asks for accessible information and communication technology, and government websites follow GIGW (Guidelines for Indian Government Websites), which build on WCAG. Accessibility is a real skill that appears in job descriptions.

Watch the lesson

Accessibility in depth: keyboard, ARIA and WCAG · 1/8

Usable by everyone

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)

Task 1 · Code · HTML

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").

Task 2 · Code · Javascript

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.

Task 3 · Practice

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

Question 1

1.Which tabindex value should you never use?

Question 2

2.A button shows only a magnifying-glass icon. How do you give it an accessible name?

Question 3

3.What does "no ARIA is better than bad ARIA" mean?

Question 4

4.A "Saved!" message appears after the user clicks Save. How do screen reader users hear it without losing their place?

Question 5

5.What contrast ratio does WCAG AA need for normal-size body text?

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.

← Previous: Performance and Core Web Vitals