WCAG 2.2 Developer Guide
A comprehensive, practical guide to Web Content Accessibility Guidelines (WCAG) 2.2. Written for developers who want to build accessible web applications.
Understanding WCAG Conformance Levels
Perceivable
Non-text Content
All non-text content (images, icons, charts) must have text alternatives.
Always provide meaningful alt text for images. For decorative images, use empty alt attribute.
<img src="chart.png"><img src="chart.png" alt="Sales revenue increased 45% in Q4 2024">- Use descriptive alt text that conveys the same information
- Decorative images should have alt=""
- Complex images (charts, diagrams) need longer descriptions
- Icon buttons need accessible labels via aria-label
Info and Relationships
Information, structure, and relationships must be programmatically determinable.
Use semantic HTML elements to convey structure and meaning.
<div class="heading">Page Title</div>
<div class="list">
<div>Item 1</div>
<div>Item 2</div>
</div><h1>Page Title</h1>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>- Use proper heading hierarchy (h1, h2, h3)
- Use <ul>, <ol> for lists, not divs
- Use <table> with proper headers for tabular data
- Label form inputs with <label> or aria-label
Orientation
Content must not be restricted to a single display orientation (portrait/landscape).
Don't lock orientation unless essential. Support both portrait and landscape modes.
- Avoid CSS that forces specific orientation
- Test responsive design in both orientations
- Only lock orientation when absolutely necessary (e.g., piano app)
Identify Input Purpose
Input fields that collect user information must identify their purpose programmatically.
Use HTML5 autocomplete attributes to identify input purpose.
<input type="text" name="email"><input type="email" name="email" autocomplete="email">- Use autocomplete="email" for email inputs
- Use autocomplete="name" for name fields
- Use autocomplete="tel" for phone numbers
- Helps browsers auto-fill and assistive tech understand context
Contrast (Minimum)
Text must have a contrast ratio of at least 4.5:1 (3:1 for large text).
Use color contrast checkers during design. Test with browser dev tools.
/* Light gray text on white */
color: #CCCCCC;
background: #FFFFFF;/* Dark gray text on white */
color: #595959;
background: #FFFFFF;- Normal text needs 4.5:1 contrast ratio
- Large text (18pt+ or 14pt+ bold) needs 3:1
- Test with tools like Chrome DevTools or WebAIM contrast checker
- Don't rely on color alone to convey information
Reflow
Content must reflow without horizontal scrolling at 320px width (400% zoom).
Build responsive layouts that work at 320px width. Test by zooming to 400%.
- Avoid fixed widths in pixels
- Use responsive units (rem, em, %)
- Test at 400% zoom in browser
- Allow content to reflow vertically
Non-text Contrast
UI components and graphical objects must have 3:1 contrast ratio.
Ensure buttons, form inputs, icons, and focus indicators have sufficient contrast.
- Button borders need 3:1 contrast
- Form input borders need 3:1 contrast
- Focus indicators must be visible (3:1 contrast)
- Chart elements need distinguishable colors
Text Spacing
Content must not break when users adjust text spacing (line height, paragraph spacing, etc.).
Test with increased text spacing. Avoid overflow: hidden on text containers.
- Test with 1.5x line height
- Test with 2x paragraph spacing
- Avoid fixed heights on text containers
- Content should reflow, not clip
Content on Hover or Focus
Tooltips and popovers triggered by hover/focus must be dismissible, hoverable, and persistent.
Ensure tooltips can be dismissed with Esc key, can be hovered over, and don't disappear automatically.
- Tooltip must not disappear when user hovers over it
- Provide Esc key to dismiss
- Don't auto-hide unless user moves pointer away
- Ensure keyboard users can trigger and dismiss
Operable
Keyboard
All functionality must be available via keyboard.
Test your entire app using only keyboard (Tab, Enter, Space, Arrow keys).
<div onclick="submitForm()">Submit</div><button type="submit">Submit</button>- Use semantic HTML (button, a, input)
- Don't use div/span for interactive elements
- Test with Tab key for navigation
- Ensure Enter/Space activates buttons
No Keyboard Trap
Users must be able to navigate away from any component using keyboard.
Ensure modals, custom widgets don't trap keyboard focus. Tab should cycle through, Esc should exit.
- Modal dialogs should trap focus within themselves
- But Esc key should close modal and return focus
- Custom widgets must allow Tab to move focus out
- Test keyboard navigation thoroughly
Character Key Shortcuts
Single character keyboard shortcuts must be remappable or can be turned off.
If using single-key shortcuts (e.g., "S" for save), provide a way to disable or remap them.
- Avoid single-character shortcuts (conflict with screen readers)
- Use Ctrl/Cmd + key instead
- If necessary, allow users to turn off shortcuts
- Provide remapping in settings
Timing Adjustable
Users must be able to extend, adjust, or turn off time limits.
Provide options to extend timeouts, or warn users before timeout occurs.
- Session timeouts should warn users beforehand
- Provide "Extend session" option
- Auto-save form data to prevent loss
- Don't use arbitrary time limits
Pause, Stop, Hide
Moving, blinking, or auto-updating content must have pause/stop controls.
Carousels, animations, auto-updating content need pause buttons.
// Auto-play carousel with no controls
setInterval(nextSlide, 3000)// Carousel with pause button
<button onClick={pauseCarousel}>Pause</button>- Provide pause button for carousels
- Don't auto-play videos without controls
- Auto-updating content (news ticker) needs pause
- Avoid infinite auto-advance
Three Flashes or Below Threshold
Content must not flash more than 3 times per second.
Avoid flashing animations, strobe effects. Can trigger seizures.
- Never use rapid flashing effects
- Avoid animated GIFs with quick flashing
- Test animations frame-by-frame
- Large flashing areas are especially dangerous
Bypass Blocks
Provide a way to skip repeated navigation (skip links).
Add a "Skip to main content" link at the top of every page.
// No skip link<a href="#main" class="skip-link">Skip to main content</a>
<main id="main">...</main>- First focusable element should be skip link
- Link should jump to main content
- Can be visually hidden but keyboard accessible
- Helps keyboard users skip navigation
Page Titled
Every page must have a descriptive title.
Use unique, descriptive page titles. Format: "Page Name - Site Name".
<title>Page</title><title>Account Settings - Blyxo</title>- Title should describe page content
- Make titles unique across pages
- Put most specific info first
- Update title on SPA route changes
Focus Order
Keyboard focus order must be logical and intuitive.
Tab order should follow visual layout. Avoid positive tabindex values.
- Never use tabindex > 0
- Use semantic HTML for natural order
- Test tab order matches visual order
- Use tabindex="-1" to remove from tab order
Link Purpose (In Context)
Link purpose must be clear from link text or context.
Avoid "Click here" or "Read more". Use descriptive link text.
<a href="/report.pdf">Click here</a><a href="/report.pdf">Download Q4 2024 Accessibility Report (PDF, 2.3MB)</a>- Link text should describe destination
- Include file type and size for downloads
- Avoid "Click here", "Learn more"
- Screen readers often list links out of context
Multiple Ways
Provide multiple ways to find pages (navigation, search, sitemap).
Include navigation menu, search functionality, and optionally a sitemap.
- Main navigation should be consistent
- Provide search functionality
- Consider breadcrumbs for deep sites
- Sitemap can help with discoverability
Headings and Labels
Headings and labels must be descriptive.
Use clear, descriptive headings and form labels.
<h2>Details</h2>
<label>Field 1</label><h2>Account Settings</h2>
<label>Email Address</label>- Headings should describe section content
- Form labels should clearly identify purpose
- Avoid vague labels like "Input"
- Be specific and descriptive
Focus Visible
Keyboard focus must be clearly visible.
Don't remove focus outlines. Style them to be visible against all backgrounds.
/* Never do this! */
*:focus { outline: none; }*:focus {
outline: 2px solid #0066CC;
outline-offset: 2px;
}- Never use outline: none without replacement
- Focus indicator needs 3:1 contrast
- Make focus visible on all backgrounds
- Test with keyboard navigation
Focus Not Obscured (Minimum)
When an element receives focus, it must not be entirely hidden by other content.
Ensure sticky headers, footers, or modals don't completely hide focused elements.
- Scroll focused element into view
- Account for sticky headers when scrolling
- At least part of focused element must be visible
- Use scrollIntoView() with block: "nearest"
Pointer Gestures
All multipoint or path-based gestures must have single-pointer alternative.
Don't require pinch-to-zoom, two-finger swipe, etc. Provide buttons instead.
- Provide + / - buttons instead of pinch-to-zoom
- Single tap/click should work for all functionality
- Avoid requiring drag-and-drop (provide alternative)
- Path-based gestures need alternatives
Pointer Cancellation
Actions should complete on pointer up event, not down.
Use onClick, not onMouseDown. Allows users to cancel by moving pointer away.
- Use onClick instead of onMouseDown
- Allows users to cancel accidental clicks
- Exception: down-event needed (e.g., piano keys)
- Provide undo for critical actions
Label in Name
Accessible name must include visible label text.
If button shows "Submit", accessible name should include "Submit".
<button aria-label="Send form">Submit</button><button>Submit</button>
<!-- or -->
<button aria-label="Submit form">Submit</button>- Visible text should match or be included in accessible name
- Helps voice control users ("Click Submit")
- Don't override with completely different aria-label
- Accessible name can add context but must include visible text
Motion Actuation
Functionality triggered by device motion must have UI alternative.
If using shake-to-undo or tilt controls, provide button alternatives.
- Don't rely solely on device motion
- Provide button alternative to shake/tilt
- Allow users to disable motion activation
- Motion can be difficult for users with disabilities
Dragging Movements
Dragging interactions must have single-pointer alternative.
Provide buttons/inputs as alternative to drag-and-drop, sliders, etc.
- Sliders should have input field alternative
- Drag-and-drop should have cut/paste buttons
- Provide arrow buttons for reordering
- Some users can't perform dragging
Target Size (Minimum)
Interactive elements must be at least 24x24 CSS pixels.
Make buttons, links, and touch targets at least 24x24px. Ideally 44x44px.
button { width: 16px; height: 16px; }button {
min-width: 24px;
min-height: 24px;
padding: 8px;
}- Minimum: 24x24px for AA compliance
- Recommended: 44x44px for better usability
- Include padding in calculations
- Exceptions: inline links in sentences
Understandable
Language of Page
Default language of page must be programmatically determined.
Set lang attribute on html element.
<html><html lang="en">- Always set lang on <html>
- Use correct language code (en, es, fr)
- Helps screen readers pronounce correctly
- Required for translation tools
Language of Parts
Language changes within content must be marked.
When mixing languages, mark foreign text with lang attribute.
<p>The French word bonjour means hello.</p><p>The French word <span lang="fr">bonjour</span> means hello.</p>- Mark foreign phrases with lang attribute
- Helps screen readers switch pronunciation
- Apply to spans, paragraphs, or other elements
- Not needed for proper names or universal terms
On Focus
Receiving focus must not trigger unexpected context changes.
Don't automatically submit forms, navigate, or open modals on focus.
- Focus should not auto-submit forms
- Don't auto-open modals on focus
- Don't navigate to new page on focus
- User should initiate actions (click, Enter)
On Input
Changing input values must not cause unexpected context changes.
Don't auto-submit when user types or selects. Wait for explicit submit action.
- Don't auto-submit on select change
- Don't navigate on dropdown selection
- Provide explicit submit button
- Auto-save is OK if it doesn't change context
Consistent Navigation
Navigation must be consistent across pages.
Keep navigation menu in same location with same items across site.
- Navigation order should be consistent
- Don't rearrange menu items between pages
- Keep navigation in same location
- Helps users build mental model
Consistent Identification
Components with same functionality must be identified consistently.
Use same icon, label, and text for same actions across site.
- Save button should always look/say the same
- Search icon should be consistent
- Don't call it "Search" on one page, "Find" on another
- Consistency aids recognition and learning
Consistent Help
Help mechanisms must be in consistent locations.
If you provide help links/chat/contact, keep them in same place on every page.
- Help link should be in same location
- Chat widget in consistent corner
- Contact info in same place (e.g., footer)
- Relative order should stay the same
Error Identification
Form errors must be clearly identified and described.
Show clear error messages next to fields. Use aria-invalid and aria-describedby.
<input type="email" class="error"><input type="email" aria-invalid="true" aria-describedby="email-error">
<div id="email-error" role="alert">Please enter a valid email address</div>- Use aria-invalid="true" on error fields
- Link to error message with aria-describedby
- Use role="alert" for error messages
- Describe what's wrong and how to fix it
Labels or Instructions
Form inputs must have clear labels or instructions.
Every input needs a visible label. Provide format requirements and constraints.
<input type="text" placeholder="Email"><label for="email">Email Address</label>
<input id="email" type="email" required>- Use <label> element for every input
- Don't rely on placeholder alone
- Explain format requirements (e.g., "MM/DD/YYYY")
- Mark required fields clearly
Error Suggestion
Error messages should suggest how to fix the error.
Don't just say "Invalid input". Explain what's wrong and how to correct it.
"Error: Invalid date""Please enter date in MM/DD/YYYY format (e.g., 12/31/2024)"- Explain what's wrong
- Show correct format or example
- Suggest corrections when possible
- Be specific and helpful
Error Prevention (Legal, Financial, Data)
Critical actions must be reversible, verified, or confirmed.
For legal/financial/data actions, provide confirmation step or allow undo.
- Show confirmation dialog before submitting
- Allow review and edit before final submit
- Provide undo functionality
- Especially important for purchases, agreements, data deletion
Redundant Entry
Don't require users to enter the same information twice in one process.
Auto-fill previously entered data. Don't make users re-enter shipping address for billing.
- Pre-populate fields with known data
- Provide "Same as shipping" checkbox
- Use autocomplete attributes
- Don't make users re-enter what you already know
Accessible Authentication (Minimum)
Authentication must not rely solely on cognitive function tests.
Don't require users to solve puzzles or transcribe distorted text. Use alternative methods.
- Avoid CAPTCHAs that require solving puzzles
- Provide alternative auth methods (magic link, passkey)
- Allow password managers to work
- Don't require remembering complex patterns
Robust
Parsing
HTML must be well-formed (deprecated in WCAG 2.2 - browsers now handle this).
Write valid HTML. Use linters to catch syntax errors.
- Close all tags properly
- Don't duplicate IDs
- Use valid HTML syntax
- This criterion is deprecated but still good practice
Name, Role, Value
All UI components must have programmatically determinable name, role, and value.
Use semantic HTML or ARIA to expose component information to assistive tech.
<div onclick="toggle()">Toggle</div><button type="button" aria-pressed="false">Toggle</button>- Use semantic HTML (button, checkbox, etc.)
- Add ARIA roles for custom components
- Ensure state changes are announced (aria-pressed, aria-expanded)
- Test with screen reader
Status Messages
Status messages must be announced to screen readers without receiving focus.
Use role="status" or role="alert" for dynamic content updates.
<div id="message">Item added to cart</div><div role="status" aria-live="polite">Item added to cart</div>- Use role="status" for non-urgent updates
- Use role="alert" for important/urgent messages
- Don't move focus to status messages
- Screen reader will announce automatically
Need Help Testing Your Site?
Blyxo automatically tests your website against all WCAG 2.2 guidelines and provides detailed, actionable reports with code-level remediation guidance.
Request a Demo