WCAG is the standard everyone is measured against — hit Level AA with sufficient color contrast, labelled accessible forms, support for reduced motion, and correct page language and titles.
Why: low-contrast text is unreadable for people with low vision or anyone in bright sunlight. WCAG AA requires a contrast ratio of at least 4.5:1 for normal text (3:1 for large text and UI components). Note: don't eyeball it — your browser's DevTools shows the exact ratio and a pass/fail right in the color picker.
/* ❌ Light grey on white — about 2.3:1, fails AA */
.muted { color: #aaaaaa; background: #ffffff; }
/* ✅ Darker grey — about 4.6:1, passes AA for normal text */
.muted { color: #767676; background: #ffffff; }
/* Also: never rely on color ALONE to convey meaning —
pair an error color with an icon or text, for color-blind users. */Why: forms are where accessibility most often breaks. Every input needs a real <label> tied to it (so clicking the label focuses the field and the screen reader announces it), errors must be described in text and linked to the field, and required fields must say so. Note: placeholder text is NOT a label — it vanishes when typing and often fails contrast.
<!-- Label linked via for/id; error linked via aria-describedby -->
<label for="email">Email address</label>
<input
id="email"
type="email"
required
aria-describedby="email-err"
aria-invalid="true"
/>
<p id="email-err">Enter a valid email, e.g. you@example.com</p>Why: large animations, parallax, and auto-playing motion can cause nausea, dizziness, or migraines for people with vestibular disorders. The prefers-reduced-motion media query lets those users — who set it at the OS level — opt out, and you honor it in CSS. Note: keep essential feedback, but drop the decorative movement.
/* Default: a smooth animated transition */
.card { transition: transform 0.3s ease; }
/* When the user asked their OS to reduce motion, turn it off */
@media (prefers-reduced-motion: reduce) {
.card { transition: none; }
*,
*::before,
*::after { animation-duration: 0.01ms !important; }
}Why: the lang attribute on <html> tells screen readers which language to pronounce the page in — without it, English read with a French voice is gibberish. And a unique, descriptive <title> per page is the first thing announced and what labels the browser tab and bookmarks. Note: these are two of the easiest WCAG wins and are pure HTML.
<!doctype html>
<html lang="en">
<head>
<!-- Unique and descriptive, not just "Home" on every page -->
<title>Billing settings — Acme Dashboard</title>
</head>
<body>…</body>
</html>Why: tools catch maybe a third of issues automatically; the rest need a human. Combine an automated checker (the Lighthouse "Accessibility" audit, or the axe DevTools extension) with two manual checks: tab through the whole page with the keyboard, and listen to a screen reader. Note: every OS ships one free — VoiceOver on macOS/iOS, Narrator on Windows, TalkBack on Android.
# Automated pass: run Lighthouse's Accessibility category in Chrome DevTools,
# or install the free "axe DevTools" browser extension and scan the page.
# Manual passes (the part tools can't do):
# 1. Unplug the mouse — reach and operate everything with Tab + Enter.
# 2. Turn on a screen reader and navigate by headings and landmarks;
# does every control announce a clear name and state?