Web Accessibility WCAG Essentials Every Dev Should Know
A practical guide to the WCAG fundamentals: semantic HTML, keyboard support, contrast, focus management, and ARIA used responsibly.
What you'll learn
- ✓The four WCAG principles and what they mean in code
- ✓How semantic HTML solves most accessibility problems
- ✓How to build keyboard-friendly interactions
- ✓How to use ARIA only when needed
- ✓How to test accessibility without specialized hardware
Prerequisites
- •Basic HTML knowledge
Accessibility is not a separate feature. It is a property of the same HTML and CSS you already write. WCAG, the Web Content Accessibility Guidelines, gives you a checklist, but the day-to-day work is mostly about using the right element, exposing state correctly, and making sure the keyboard can reach everything.
The four principles
WCAG groups requirements under POUR:
- Perceivable: users can perceive the content. Text alternatives, captions, contrast.
- Operable: users can operate the interface. Keyboard, time, motion.
- Understandable: users can understand the content. Language, predictability, error help.
- Robust: tools can interpret the content. Valid HTML, correct roles.
Most teams aim for WCAG 2.1 or 2.2 at level AA. AAA is stricter and often unrealistic across a whole site.
Semantic HTML does the heavy lifting
The single most effective thing you can do is use the right element. A button is a button. A link goes somewhere. A heading describes a section.
<!-- bad -->
<div class="btn" onclick="save()">Save</div>
<!-- good -->
<button type="button" onclick="save()">Save</button>
The bad version is not focusable, not announced as a button, and not activated by Enter or Space. The good version gets all of that for free.
Use:
buttonfor actions on the same page.a hreffor navigation.h1throughh6for outline, not for font size.labelfor every form control.nav,main,header,footerfor landmarks.
Labels and names
Every interactive element needs an accessible name. For inputs, prefer a real label:
<label for="email">Email</label>
<input id="email" type="email" name="email">
If you cannot show a visible label, use aria-label or aria-labelledby:
<button aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>
Decorative icons should have aria-hidden="true" so they are not announced.
Images need an alt attribute. Use empty alt="" for decorative images. Write alt text that describes the meaning, not the pixels.
Keyboard support
If a user cannot reach or operate your interface with a keyboard, it fails. Two non-negotiables:
- Tab and Shift+Tab move through interactive elements in a sensible order.
- Enter or Space activates the focused control.
Common traps:
- Custom widgets that swallow focus. Always provide a way out with Tab or Escape.
- Modals that allow background focus. Trap focus inside the dialog while it is open and restore it to the trigger on close.
- Click handlers on non-interactive elements. Either switch to
buttonor addrole="button",tabindex="0", and a key handler. Switching the element is almost always better.
Test by unplugging the mouse. If you cannot complete the task, real users cannot either.
Focus visibility
Never remove the focus outline without replacing it. Many designs hide :focus because the default outline is ugly with a mouse. Use :focus-visible instead:
button:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
This shows the outline for keyboard focus and hides it for mouse users.
Color and contrast
WCAG AA requires:
- 4.5:1 contrast for normal text.
- 3:1 for large text (about 24px regular or 18px bold).
- 3:1 for graphical UI like form borders and focus indicators.
Color must not be the only signal. A red error message also needs an icon or text. Form validation should not rely on border color alone.
Tools: Chrome DevTools shows contrast in the color picker. axe and Lighthouse audit pages in seconds.
ARIA: the rule of least power
ARIA lets you describe roles and states that HTML cannot express on its own. The first rule of ARIA is to avoid it when a native element does the job.
When you do need it, follow the patterns in the WAI ARIA Authoring Practices. A few common ones:
<button aria-expanded="false" aria-controls="menu-1">Menu</button>
<ul id="menu-1" hidden>...</ul>
<div role="alert">Failed to save. Try again.</div>
<div role="dialog" aria-modal="true" aria-labelledby="title">
<h2 id="title">Confirm delete</h2>
...
</div>
Three rules:
- Do not change
rolelightly. Screen readers depend on consistency. - Keep
aria-state attributes in sync with reality. A stalearia-expandedis worse than none. - Do not add
tabindexgreater than 0. It scrambles tab order across the page.
Forms that help users recover
- Associate every input with a label.
- Mark required fields visually and with
aria-required="true". - Show errors near the field, not just at the top. Link to them with
aria-describedby. - Do not validate on every keystroke. Validate on blur or submit, then announce errors politely.
<label for="email">Email</label>
<input id="email" type="email" aria-describedby="email-err" aria-invalid="true">
<p id="email-err">Enter a valid email address.</p>
Motion, time, and media
- Respect
prefers-reduced-motion. Cut or shorten non-essential animations. - Avoid auto-playing media with sound.
- Provide captions for video, transcripts for audio.
- Give users enough time. If a session expires, warn them and let them extend.
Testing without specialized hardware
You will not catch everything without testing on a real screen reader, but you can catch a lot with a short loop:
- Tab through the page. Can you reach and use every control?
- Zoom to 200 percent. Does anything overflow or disappear?
- Run axe DevTools or Lighthouse. Triage the issues.
- Turn on VoiceOver on macOS or NVDA on Windows. Navigate the page by headings and landmarks.
- Try the page with
prefers-reduced-motionand a forced color scheme.
Add an automated accessibility check to your test suite. Tools like @axe-core/playwright catch regressions before they ship.
Wrap up
Accessibility work concentrates in a small number of habits: use real elements, label everything, keep focus visible, write text that survives without color, and add ARIA only when HTML cannot express the meaning. Most of WCAG falls out of those habits. The rest is cleanup.