Deck 6 - Accessibility + Semantics + ARIA (objective)
Goal: spot and fix a11y/semantic issues (WCAG-minded) without over-ARIA.
You practice: semantic HTML, labeling, keyboard support, focus management, and ARIA patterns.
Output style: 1 verdict sentence + 2-3 issues + safest fix approach.
A11y baseline rule (most important).
Use semantic HTML first; add ARIA only to fill gaps. Avoid role overrides that break native semantics.
WCAG in practice (what evaluators look for).
Keyboard access, visible focus, proper labels/names, correct roles/states, readable structure, and announced async changes.
Accessible name (definition).
The label screen readers use for a control. Sources: <label>, aria-label, aria-labelledby, and sometimes text content.</label>
Labeling inputs (best practice).
Prefer <label>. Alternatives: aria-label or aria-labelledby. Placeholder is not a label.</label>
Buttons vs divs (rule).
Clickable UI should be <button> (or <a>) not <div onClick>. If custom, must add role, tabIndex, and keyboard handlers.</a></button>
Links (rule).
Use <a> for navigation, <button> for actions. Do not use <a> for actions.</a></button></a>
Headings (structure).
Use h1-h6 in order to create a logical outline. Do not skip levels for styling.
Lists and tables (semantics).
Use <ul>/<ol>/<li> for lists. Use tables for data grids, with proper <th scope> and captions when needed.
Images (alt text).
Meaningful images need alt text; decorative images should use alt=’’ and aria-hidden=’true’ when appropriate.
ARIA roles (when to use).
Use ARIA roles for custom widgets only. Do not add role=’button’ to <button>, role=’link’ to <a>, etc.</a></button>
aria-* states (common).
aria-expanded for toggles, aria-pressed for toggle buttons, aria-selected for tabs/options, aria-disabled (avoid if native disabled works).
Keyboard interaction patterns (baseline).
Enter/Space activate buttons; arrow keys for menus/tabs; Escape closes popovers/modals; Tab moves focus.
Focus management (common).
On open: move focus into modal/popover. On close: restore focus. Trap focus in modal. Avoid focus loss on rerenders.
Dialogs/modals (baseline).
Use role=’dialog’ with aria-modal=’true’, label via aria-labelledby, and focus trap/restore.
Live regions (async updates).
Use aria-live=’polite’ for non-urgent updates; role=’alert’ for errors. Keep messages concise.
Form validation messaging.
Associate errors with inputs (aria-describedby). Use aria-invalid when invalid. Do not rely on color alone.
Disabled vs aria-disabled.
Prefer native disabled (removes from tab order). aria-disabled keeps focusable; use only when needed.
Avoiding duplicate IDs (React).
IDs must be unique. Use useId() for stable unique IDs across server/client rendering.
Testing a11y basics (RTL).
getByRole + name is an a11y assertion. Ensure elements are reachable and labeled; role=alert for errors.
Gotcha: clickable div without keyboard.
function X() {
return <div onClick={save}>Save</div>;
}Verdict: FAIL
- Issue: non-semantic interactive element; not keyboard accessible.
- Fix: use <button>Save</button> (or implement role+tabIndex+keys if custom).
Gotcha: missing label for input.
<input id='q' placeholder='Search' />
Verdict: FAIL
- Issue: placeholder is not a label.
- Fix: add <label>Search</label> or aria-label.
Gotcha: label not associated.
<label>Search</label> <input id='q' />
Verdict: FAIL
- Issue: missing htmlFor.
- Fix: <label>Search</label>.
Gotcha: button missing type in a form.
<form>
<button onClick={onCancel}>Cancel</button>
</form>Verdict: PARTIAL FAIL
- Issue: default type is submit.
- Fix: type=’button’ for non-submit buttons.
```
```