πŸŽ“ Accessibility training

Learn accessibility by example

Each lesson below shows the same component built two ways: an inaccessible version and an accessible one. The demos are live, so try them with your keyboard (use Tab, Enter and Space) and, if you can, a screen reader. Then read what changed and why it matters.

βœ• Inaccessible What to avoid βœ“ Accessible Do this instead

What else is on this site

01

Visible focus indicators

Keyboard users need to see where they are. Removing the focus outline leaves them lost.

WCAG 2.4.7 Focus Visible (AA) Β· 2.4.13 Focus Appearance (AAA)

βœ• Inaccessible

Press Tab through these. Can you tell which one is focused?

outline: none with no replacement hides focus entirely.

βœ“ Accessible

Press Tab and a clear ring follows you.

A high-contrast :focus-visible ring keeps every control discoverable.

Show the code

βœ• Avoid CSS

button:focus {
  outline: none;
}

Hides the focus ring for every keyboard user.

βœ“ Better CSS

button:focus-visible {
  outline: 3px solid #1a73e8;
  outline-offset: 2px;
}

A high-contrast ring, shown only when navigating by keyboard.

02

Use real buttons

A styled <div> looks like a button but can't be reached or operated with a keyboard.

WCAG 2.1.1 Keyboard Β· 4.1.2 Name, Role, Value

βœ• Inaccessible

Try to reach and activate this with the keyboard. You can't.

Add to cart

Cart: 0

A <div onclick> has no role, isn't focusable, and ignores Enter/Space.

βœ“ Accessible

Tab to it, press Enter or Space, and it works.

Cart: 0

A native <button> is focusable, announced as a button, and keyboard-operable for free.

Show the code

βœ• Avoid

<div class="btn"
     onclick="add()">Add</div>

Not focusable, and it ignores Enter and Space.

βœ“ Better

<button type="button"
        onclick="add()">Add</button>

Focusable, keyboard-operable and announced as a button.

03

Labelled fields & clear errors

Placeholders aren't labels, and colour alone can't communicate an error.

WCAG 1.3.1 Β· 3.3.1 Error Identification Β· 3.3.2 Labels Β· 1.4.1 Use of Colour

βœ• Inaccessible

Submit empty. Errors are red-only, with no labels.

No <label>, placeholders vanish on typing, and errors rely on colour with no text.

βœ“ Accessible

Submit empty. Errors are announced, with text + icon.

Real labels, aria-describedby errors, a focus-managed summary, and errors shown with an icon rather than colour alone.

Show the code

βœ• Avoid

<input placeholder="Email">

No label, and the placeholder vanishes as soon as you type.

βœ“ Better

<label for="email">Email</label>
<input id="email" type="email"
       aria-describedby="err">
<span id="err">Enter a valid email.</span>

Real label, and the error is tied to the field with aria-describedby.

04

Sufficient colour contrast

Low-contrast text is hard to read in sunlight, on cheap screens, or with low vision.

WCAG 1.4.3 Contrast (Minimum): 4.5:1 for body text

βœ• Inaccessible

Subscribe to our newsletter for weekly tips and updates.

Contrast β‰ˆ 1.3:1, fails

Light-grey text on a grey card is decorative at best and unreadable for many.

βœ“ Accessible

Subscribe to our newsletter for weekly tips and updates.

Contrast β‰ˆ 15.8:1, passes AAA

Dark text on a light surface clears AA (4.5:1) comfortably and stays legible everywhere.

Show the code

βœ• Avoid CSS

.note {
  color: #a3a3a3;   /* on #8e8e8e */
}  /* β‰ˆ 1.3:1, fails */

Light grey on grey is unreadable for many.

βœ“ Better CSS

.note {
  color: #1a1a1a;   /* on #f4f4f0 */
}  /* β‰ˆ 15.8:1, passes */

Dark on light clears AA (4.5:1) with room to spare.

05

Meaningful alt text

Screen readers read alt aloud. Missing or junk alt text leaves blind users guessing.

WCAG 1.1.1 Non-text Content

βœ• Inaccessible

A screen reader announces: "image 4F7A9C.jpg"

No alt falls back to the filename. An alt="image" is just noise.

βœ“ Accessible

A screen reader announces: "Aerial view of a Maldivian island with turquoise lagoon"

Describe the purpose. Purely decorative images get alt="" so they're skipped.

Show the code

βœ• Avoid

<img src="island.jpg">

With no alt, a screen reader reads the filename out loud.

βœ“ Better

<img src="island.jpg"
     alt="Aerial view of a Maldivian
          island and turquoise lagoon">

Describe the purpose. Decorative images get alt="".

06

Large enough touch targets

Tiny, crowded controls are hard to tap, especially with tremors or large fingers.

WCAG 2.5.8 Target Size (Minimum): at least 24Γ—24px, ideally 44Γ—44px

βœ• Inaccessible

Try tapping just one icon on a phone.

~20px targets with no spacing cause mis-taps and frustration.

βœ“ Accessible

Comfortable to hit, first time.

44Γ—44px targets with spacing are easy and forgiving for everyone.

Show the code

βœ• Avoid CSS

.icon-btn {
  width: 20px;
  height: 20px;
}

~20px targets cause mis-taps.

βœ“ Better CSS

.icon-btn {
  min-width: 44px;
  min-height: 44px;
}

44Γ—44px (min 24Γ—24 for WCAG 2.5.8 AA) is easy to hit.

08

Logical heading structure

Screen-reader users navigate by headings. Skipped levels or fake "headings" break that map.

WCAG 1.3.1 Info & Relationships Β· 2.4.6 Headings & Labels

βœ• Inaccessible

Bold text that only looks like headings:

Our Services

Web Audits

Training

A styled <p> carries no heading role and is invisible to heading navigation. Jumping from level 1 to 3 also skips a level.

βœ“ Accessible

Real, properly nested headings:

h2 Our Services

h3 Web Audits

h3 Training

Convey structure with real headings: nest h2 β†’ h3 in order without skipping levels, and style them with CSS. Never pick a tag for its size. A single h1 for the page's main topic is a solid convention (multiple h1s aren't an automatic failure, but one is easier to reason about).

Show the code

βœ• Avoid

<p class="title">Our services</p>
<p class="sub">Web audits</p>

Styled paragraphs carry no heading role.

βœ“ Better

<h2>Our services</h2>
  <h3>Web audits</h3>

Real, properly nested headings, styled with CSS rather than tag size.

09

Name your icon-only buttons

An icon button with no text leaves a screen reader with nothing to announce but "button".

WCAG 4.1.2 Name, Role, Value Β· 1.1.1 Non-text Content

βœ• Inaccessible

Announced as: "button … button … button"

The emoji is decorative to the accessibility tree, so each control is just an unnamed "button".

βœ“ Accessible

Announced as: "Search, button … Notifications, button … Settings, button"

An aria-label (or visually-hidden text) gives each button a clear, spoken name.

Show the code

βœ• Avoid

<button>πŸ”</button>

Announced only as β€œbutton”.

βœ“ Better

<button aria-label="Search">πŸ”</button>

aria-label gives the icon button a spoken name.

10

Modal dialogs & focus management

A real dialog traps focus, closes on Esc, and returns focus when dismissed.

WCAG 2.4.3 Focus Order Β· 2.1.2 No Keyboard Trap Β· 1.3.1

βœ• Inaccessible

Open it, then press Tab. Focus drifts to the page behind.

A plain <div> overlay has no dialog role, doesn't trap or restore focus, and ignores Esc.

βœ“ Accessible

Open it and focus moves inside, Esc closes, focus returns.

The native <dialog> element handles focus trapping, Esc, and focus return for you.

Show the code

βœ• Avoid

<div class="overlay" id="m">…</div>
open.onclick = () =>
  m.style.display = "block";

No focus trap, no Esc, focus lost on close.

βœ“ Better

<dialog id="m">…</dialog>
open.onclick = () =>
  m.showModal();

The native <dialog> traps focus, handles Esc and restores focus.

Subscribe?

Focus is trapped here. Try Tab, then press Esc, and focus returns to the button.

? Why it is worth doing

Ten fixes is the easy part. The harder part is the meeting where somebody asks how many of your users are really disabled. This is the answer, and it is the same answer that got dropped kerbs built in the first place.

Four different people all using the same dropped kerbA dropped kerb in a pavement, with four users approaching it from above. A wheelchair user, the group it was originally built for, then a parent pushing a pram, a traveller pulling a wheeled suitcase, and a delivery trolley. All four arrows point to the same single kerb cut. The curb cut effect. Built for one group. Used by everyone, every day.The curb cut effectBuilt for one group. Used by everyone, every day.wheelchair userthe reason it was builtparent with a pramtraveller with a casedelivery trolleyone dropped kerb
This is the argument to use when someone asks how many of our users are really disabled.

Things built for a specific disabled need keep turning out to serve everybody. Captions, voice control, dark mode and predictive text all arrived the same way. Part 1 of the course makes the whole case, with the numbers.

β–Ά Test it yourself

You don't need fancy tools to catch most issues. Start here, then work through the full testing guide when you want the screen reader keystrokes and a record sheet:

⌨️ Unplug your mouse

Navigate the whole page with Tab, Shift+Tab, Enter, Space and arrow keys. Can you reach and use everything? Can you always see focus?

πŸ”Š Turn on a screen reader

VoiceOver (⌘+F5 on Mac) or NVDA (free, Windows). Close your eyes and listen. Does it make sense?

πŸ” Zoom to 200%

Press Ctrl/⌘ + a few times. Does content reflow without horizontal scrolling or clipping?

πŸ§ͺ Run axe DevTools (opens in a new tab)

A free browser extension that catches many issues automatically. Great first pass, but no tool replaces manual testing.

Want the whole method? Part 4 of the team training covers a fifteen minute pass, keyboard testing, real NVDA, VoiceOver and TalkBack keystrokes, what automated tools miss, and how to write a finding up.

How to test what you build β†’
↑