Accessibility (A11y) Checks

Practical accessibility items aligned with WCAG 2.1 AA that are most often missed. Accessible UI = more users + legal compliance.
Critical

Use semantic HTML; end the <div> stacks

Why it matters

Screen readers and search engines infer structure from tags. Using <div onclick> instead of <button> means you must reimplement keyboard, enter, role, focus — and you'll miss something.

How to apply

<header>, <nav>, <main>, <article>, <aside>, <footer>. Clickable → <button> or <a>. Form controls → <label>, <fieldset>, <legend>. Lists → <ul>/<ol>/<li>.

Example
Wrong: <div class="btn" onclick=...>Submit</div>. Right: <button type="submit">Submit</button>.
Critical

Write meaningful alt text on images

Why it matters

Screen reader users, people with vision impairments, and anyone whose image failed to load depend on alt text. Missing alt = missing content.

How to apply

Describe the content on informative images; on decorative ones use alt="". If an icon is next to its text, mark the icon aria-hidden="true". Write helpful descriptions, not keyword stuffing.

Example
Wrong: <img src="logo.png" alt="logo">. Right: <img src="logo.png" alt="Duck — wizard from idea to architecture"> or alt="" if decorative.
Critical

Color contrast at least 4.5:1 for normal text

Why it matters

Low contrast is unreadable for low-vision users and anyone using a phone outdoors. WCAG AA requires 4.5:1 for normal text and 3:1 for large text.

How to apply

Test palette for contrast at design time (Figma Contrast, contrast-ratio.com). Avoid light-gray text on gray. Don't use placeholder as a value indicator; #999 fails contrast.

Example
Bootstrap 5: text-muted default #6c757d (4.54:1 on white — passes AA). Lighter than that fails AA.
Critical

Keep a visible focus indicator

Why it matters

Keyboard, screen reader, and switch users rely on the focus ring to know where they are. 'outline: none' is the most common a11y mistake.

How to apply

Never globally strip outline. For custom design, redefine the ring via :focus-visible (sufficient contrast, ≥2px thick). :focus-visible triggers only on keyboard, not mouse.

Example
button:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
Critical

All interactions must work with keyboard

Why it matters

Anyone who can't use a trackpad/mouse depends on the keyboard. Menus that appear only on hover, cards that only move via drag — these block many users.

How to apply

Natural tab order. Trap focus inside modals and return it on close. Dropdowns respond to arrow keys + enter. Escape closes modal/dropdown. Provide a keyboard alternative for drag.

Example
dnd-kit: enable the keyboard sensor → space to 'grab', arrows to move, space to 'drop'.
High

Always label form controls

Why it matters

Placeholder disappears and doesn't replace the label. Without a label, a screen reader just says 'text field' and the user has no idea what to type.

How to apply

<label for="id">...</label> + <input id="id">, or wrap input in label. For icon buttons use aria-label. For groups (e.g. radios) use <fieldset>/<legend>. Mark required with aria-required.

Example
<label for="email">Email</label><input id="email" type="email" required aria-required="true">
High

Use ARIA only when necessary — prefer native HTML

Why it matters

'No ARIA is better than bad ARIA.' Wrong roles/states actively break accessibility. Most needs are already met by native HTML.

How to apply

<button> beats role="button". For custom patterns (combobox, tablist, tree), follow WAI-ARIA Authoring Practices. Add aria-live="polite"/"assertive" for dynamic content.

Example
Toast notification: <div role="status" aria-live="polite">content</div> gets announced by screen readers.
Medium

Add a skip link — so keyboard users can bypass the nav

Why it matters

Tabbing through the same 20–50 menu items on every page is exhausting. A skip link jumps straight to main content.

How to apply

The first focusable element on the page should be a 'Skip to content' link. Hidden by default, visible on focus.

Example
<a class="visually-hidden-focusable" href="#main">Skip to content</a> + <main id="main">…</main>
High

Don't rely on color alone — add a secondary signal

Why it matters

Color-blind users can't distinguish red/green. If state is conveyed only by color, those users get no information.

How to apply

Add an icon + text next to error color. Mark required fields with (*), not only a red border. In charts, add pattern/shape variations.

Example
Form: red border + ⚠ icon + 'Invalid email' text. Chart: distinguish lines with dash/dot patterns, not only color.
Medium

Respect reduced-motion preference

Why it matters

Aggressive parallax/animation can trigger vertigo for users with vestibular sensitivity. If you ignore OS-level 'reduce motion', they literally can't stay on the site.

How to apply

Use the prefers-reduced-motion: reduce media query to shorten or disable animations. Require opt-in for auto-playing video.

Example
@media (prefers-reduced-motion: reduce) { * { animation-duration: .01ms !important; transition-duration: .01ms !important; } }
Medium

Declare page and section language

Why it matters

Screen readers need the language to pronounce correctly. Wrong lang → Turkish text read with English pronunciation = gibberish.

How to apply

<html lang="en"> or dynamic to reflect the user's language. Mark a foreign-language quote via span lang="tr".

Example
In Duck's Layout: <html lang="@L.CurrentCode"> → correct voice when switching TR/EN.
Medium

Add captions and transcripts to media

Why it matters

Captions are essential for quiet environments and hearing-impaired viewers. Transcripts help search engines and skimming readers.

How to apply

Provide WebVTT subtitles for video (<track kind="captions">). Publish a text transcript for podcasts. Auto-generation (Whisper) is acceptable but proofread.

Example
<video><track kind="captions" src="subs.en.vtt" srclang="en" label="English" default></video>