Blog

Blog · Best Practices

Accessibility Best Practices in 2026: A Practical WCAG Guide

Modern web accessibility best practices aligned to WCAG 2.2 — semantic HTML, keyboard access, color contrast, form labels, focus management, ARIA restraint and the new 2.2 criteria — each with why and a code example.

XCODX Team · 7 min read

Accessibility is not a checklist you bolt on at the end — it is a set of habits that make your site usable by everyone, including keyboard-only users, screen-reader users and people with low vision or motor and cognitive differences. Most of it is cheaper to build in than to retrofit. Here are 15 accessibility best practices for 2026, aligned to WCAG 2.2 and the POUR principles (Perceivable, Operable, Understandable, Robust), each with the reasoning and a code example.

1. Use semantic HTML first

Native elements come with roles, states, keyboard behavior and focus management that assistive tech understands for free, and semantic structure (headings, landmarks, lists) is how screen-reader users navigate. Don’t skip heading levels for visual sizing — use CSS for size, headings for structure.

❌ <div class="btn" onclick="save()">Save</div>
✅ <button type="button" onclick="save()">Save</button>

<header>…</header>
<nav aria-label="Primary">…</nav>
<main><h1>Page title</h1></main>
<footer>…</footer>

2. Provide meaningful alt text (and empty alt for decorative)

alt is the text alternative a screen reader announces. Informative images need descriptive alt; purely decorative images need an *empty* alt="" so they are skipped. Omitting alt entirely is not the same — a missing alt makes many screen readers announce the file path.

✅ <img src="chart.png" alt="Sales rose 40% from Q1 to Q2 2026">
✅ <img src="divider.png" alt="">        <!-- decorative: skipped -->
❌ <img src="chart.png">                  <!-- may read the filename -->

3. Ensure full keyboard accessibility

Many users never use a mouse, so every interactive control must be reachable and operable by keyboard, with a visible focus indicator, a logical tab order and no keyboard traps. Native controls are keyboard-operable by default; never remove focus outlines without a replacement, and avoid positive tabindex values, which override natural order.

<a href="/next">Next</a>
<button>Submit</button>

/* never remove the outline outright */
:focus-visible { outline: 3px solid #1a73e8; outline-offset: 2px; }

4. Meet color contrast ratios

Low contrast is one of the most common failures and affects low-vision users, aging eyes and anyone in bright sunlight. WCAG 2.2 AA requires ≥ 4.5:1 for normal text, ≥ 3:1 for large text, and ≥ 3:1 for non-text UI components (input borders, icons, focus indicators). Contrast applies over the *actual* background, including images — test the worst-case pixel.

❌ #999999 text on #ffffff  -> 2.85:1 (fails)
✅ #767676 text on #ffffff  -> 4.54:1 (passes normal text)

5. Never rely on color alone to convey meaning

Colorblind users (roughly 1 in 12 men) and screen-reader users miss information encoded only in hue — like "required fields are red" or "green means success". Pair color with an icon, text label, pattern or underline so the meaning survives in grayscale. Charts and status indicators are frequent offenders.

❌ <span style="color:red">Payment failed</span>
✅ <span style="color:#b00020">
     <svg aria-hidden="true">…✕…</svg> Error: Payment failed
   </span>

6. Label form inputs and associate errors

Every input needs a programmatically-associated label so screen readers announce its purpose, and errors must be identifiable in text and linked to their field. A placeholder is not a label — it disappears on input, has poor contrast and is not reliably announced.

<label for="email">Email address</label>
<input id="email" name="email" type="email"
       aria-describedby="email-err" aria-invalid="true" required>
<p id="email-err" role="alert">Enter a valid email address.</p>

7. Use ARIA only when necessary — the first rule of ARIA

The first rule of ARIA is: if a native element with the semantics and behavior you need exists, use it. Bad ARIA actively breaks accessibility — studies find pages *with* ARIA average more detected errors. ARIA changes only how assistive tech *perceives* an element; it adds no behavior, so role="button" on a div gets you no keyboard handling or focusability.

❌ <div role="button" tabindex="0" onclick="...">Save</div>
✅ <button>Save</button>

❌ <div role="heading" aria-level="1">Title</div>
✅ <h1>Title</h1>

8. Give icon-only controls an accessible name

A button containing only an icon has no text for a screen reader to announce — it is read as "button" with no purpose. Provide an aria-label, and mark the decorative icon aria-hidden="true" (and focusable="false" on SVGs) so it doesn’t compete with the label. Keep the accessible name matching any visible tooltip.

❌ <button><svg>…</svg></button>
✅ <button aria-label="Close dialog">
     <svg aria-hidden="true" focusable="false">…</svg>
   </button>

9. Respect prefers-reduced-motion

Vestibular disorders can make large motion, parallax and auto-playing animation cause nausea, dizziness or migraines, so honor the OS-level "reduce motion" preference. Don’t strip *all* feedback — reduce or replace large motion with fades or instant changes, while subtle essential transitions can remain.

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

10. Support zoom to 200% and reflow to 320px

Low-vision users zoom heavily, so text must scale to 200% without losing content or function, and content must reflow at 320 CSS px wide without a second scrollbar. Use relative units and flexible layouts rather than fixed pixel caps — and never set maximum-scale=1 or user-scalable=no, which disable pinch-zoom and fail WCAG.

❌ font-size: 14px;  height: 40px;            /* fixed caps */
✅ font-size: 1rem;  min-height: 2.5rem;       /* relative, flexible */
✅ <meta name="viewport" content="width=device-width, initial-scale=1">

Keyboard and screen-reader users need to bypass repeated navigation, so a "skip to main content" link plus proper landmarks let them jump straight to content. A skip link hidden with display:none is removed from the tab order and won’t work — hide it off-screen and reveal it on :focus.

<a class="skip-link" href="#main">Skip to main content</a>
<main id="main" tabindex="-1"> … </main>

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: 1rem; top: 1rem; }

12. Manage focus in SPAs, modals and dynamic content

In single-page apps and dialogs, focus can be lost or stranded. Dialogs must trap focus while open, move focus in on open, and return focus to the trigger on close; route changes should move focus to the new view or heading. Use the native <dialog> element with showModal() where possible — it provides focus trapping, Esc-to-close and the top layer for free.

<dialog id="confirm">
  <h2>Confirm deletion</h2>
  <button value="cancel">Cancel</button>
  <button value="delete">Delete</button>
</dialog>
<script>confirm.showModal();</script>  // traps focus, Esc closes

13. Caption and transcribe media

Deaf and hard-of-hearing users need captions for audio, and transcripts also aid search, translation and sound-off environments. Auto-generated captions are a starting point, not compliance — review them for accuracy, speaker labels and punctuation.

<video controls>
  <source src="demo.mp4" type="video/mp4">
  <track kind="captions" src="demo.en.vtt" srclang="en"
         label="English" default>
</video>
<a href="/demo-transcript">Read the full transcript</a>

14. Build accessible data tables

Screen readers use table markup to announce which row and column header a cell belongs to; without proper headers, tabular data becomes an unnavigable grid of numbers. Use <th> with scope and a <caption> for the accessible name — and never use tables for visual layout.

<table>
  <caption>Q2 2026 revenue by region</caption>
  <thead>
    <tr><th scope="col">Region</th><th scope="col">Revenue</th></tr>
  </thead>
  <tbody>
    <tr><th scope="row">North</th><td>$1.2M</td></tr>
  </tbody>
</table>

15. Apply the new WCAG 2.2 criteria (and test properly)

WCAG 2.2 added criteria targeting real barriers: a focused element must not be fully hidden by sticky bars (2.4.11); pointer targets should be ≥ 24×24 CSS px (2.5.8); drag operations need a single-pointer alternative (2.5.7); and authentication must not require a memory or puzzle test with no accessible alternative (3.3.8) — so never block paste on password fields, and support password managers and passkeys. Finally, test in layers: keyboard-only, a screen reader, and automated tools (axe/Lighthouse) — automated tools catch only 30–50% of issues.

/* 2.5.8 target size */
.icon-button { min-width: 24px; min-height: 24px; }

<!-- 3.3.8: never do this — it defeats password managers -->
❌ <input type="password" onpaste="return false">

Accessibility best practices checklist

  • Use semantic HTML and landmarks; meaningful alt (empty for decorative).
  • Everything keyboard-operable with a visible :focus-visible outline and logical tab order.
  • Meet 4.5:1 text / 3:1 UI contrast; never rely on color alone.
  • Label every input and link errors; use ARIA only when native HTML can’t.
  • Give icon buttons an accessible name; respect prefers-reduced-motion.
  • Support 200% zoom / 320px reflow (never disable pinch-zoom); add skip links.
  • Manage focus in modals and SPAs (prefer native <dialog>); caption media; build accessible tables.
  • Apply WCAG 2.2 (focus not obscured, 24px targets, drag alternatives, accessible auth); test with keyboard + screen reader + automated tools.

Frequently asked questions

What are the WCAG color contrast requirements?
WCAG 2.2 AA requires a contrast ratio of at least 4.5:1 for normal text, 3:1 for large text (≥ 24px, or ≥ 18.66px bold), and 3:1 for non-text UI components like input borders, icons and focus indicators. Test contrast over the actual background, including images and gradients.
When should I use ARIA?
Only when no native HTML element provides the semantics and behavior you need. The first rule of ARIA is to prefer native elements — a real <button>, <input> or <nav> — because they include keyboard behavior and roles for free. ARIA only changes how assistive tech perceives an element; it adds no behavior, and incorrect ARIA is worse than none.
Is a placeholder a substitute for a label?
No. A placeholder disappears as soon as the user types, often fails contrast and is not reliably announced as the field’s name. Always provide an associated <label> (or aria-label/aria-labelledby when a visible label is impossible); a placeholder can be an extra hint, not the label.
Can I rely on automated accessibility tools?
They are necessary but not sufficient. Tools like axe, Lighthouse and WAVE catch roughly 30–50% of issues — mostly contrast, missing alt and missing labels. Focus order, meaningful alt quality, logical reading order and keyboard operability require manual testing with a keyboard and a screen reader. A page can score 100 in Lighthouse and still be unusable.

Build and check accessible markup live in XCODX Studio — preview semantic HTML, focus styles and ARIA as you write. For the building blocks, see our HTML best practices and CSS best practices.