Mobile and Touch Accessibility
1. Touch Target Size (minimum 44x44px) and Spacing
| Target Type | Minimum Size (WCAG 2.2) | Recommended Size | Minimum Spacing |
|---|---|---|---|
| Primary actions (buttons, links) | 24×24 CSS pixels WCAG 2.5.8 AA | 44×44 CSS pixels (iOS), 48×48 dp (Android) | 8px between targets |
| Critical interactive elements | 24×24 CSS pixels | 48×48 CSS pixels minimum | 12px spacing preferred |
| Text input fields | 24px height minimum | 44-48px height (includes padding) | 16px vertical spacing |
| Icon-only buttons | 24×24 CSS pixels | 48×48 CSS pixels (includes padding) | 8px on all sides |
| Inline text links | Exception: sentence flow allowed | Increase line-height to 1.5+ for easier tapping | Underline + adequate spacing |
| Toggle switches | 44px width × 24px height minimum | 51px × 31px (iOS standard) | 12px spacing |
| Checkbox/Radio buttons | 24×24 CSS pixels | 32×32 CSS pixels (visual + padding) | 16px spacing between options |
Example: Accessible touch target with proper sizing
<!-- CSS for touch targets -->
.touch-target {
min-width: 48px;
min-height: 48px;
padding: 12px;
margin: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
}
<!-- Small icon with extended touch area -->
<button class="touch-target" aria-label="Delete item">
<svg width="16" height="16" aria-hidden="true">
<use href="#icon-delete"></use>
</svg>
</button>
Touch Target Exceptions: WCAG 2.5.8 allows exceptions for inline text links, user agent
controls (native browser UI), and when target size is essential to the information being conveyed.
2. Mobile Screen Reader (VoiceOver, TalkBack) Support
| Platform | Screen Reader | Key Gestures | Testing Focus |
|---|---|---|---|
| iOS | VoiceOver |
Swipe right/left: Navigate Double-tap: Activate Three-finger swipe: Scroll Rotor: Quick navigation |
Heading navigation, landmark navigation, custom actions |
| Android | TalkBack |
Swipe right/left: Navigate Double-tap: Activate Two-finger swipe: Scroll Local context menu: Actions |
Reading order, custom actions, state announcements |
| Mobile Safari | VoiceOver (iOS) | Two-finger Z: Skip web content Rotor + swipe: Jump by element type |
Web vs native behavior, form controls, ARIA support |
| Chrome/Firefox (Android) | TalkBack | Volume key shortcuts TalkBack menu: Advanced controls |
Web navigation, custom widget support |
Example: Mobile-optimized ARIA labels and hints
<!-- Concise labels for mobile screen readers -->
<button
aria-label="Add to cart"
aria-describedby="product-price">
<span aria-hidden="true">+</span>
</button>
<span id="product-price" class="sr-only">$29.99</span>
<!-- Custom actions for swipe gestures (iOS) -->
<div role="article"
aria-label="Product: Wireless Headphones"
data-voiceover-actions="delete,share,favorite">
<!-- Content -->
</div>
Mobile Screen Reader Pitfalls: Avoid excessive ARIA descriptions (verbose announcements),
ensure touch targets don't overlap (causes navigation confusion), and test swipe-to-delete patterns with actual
screen readers.
3. Gesture Accessibility Alternatives
| Gesture Pattern | WCAG Requirement | Alternative Method | Implementation |
|---|---|---|---|
| Swipe to delete | Provide visible delete button 2.5.1 | Edit mode with delete buttons, long-press menu | Show delete button on item focus or in action menu |
| Pinch to zoom | Alternative zoom controls required | +/- buttons, zoom slider, double-tap zoom | Provide visible zoom controls or use native browser zoom |
| Drag and drop | Pointer-independent alternative 2.5.7 | Move up/down buttons, cut/paste, select-and-place | Provide button-based reordering mechanism |
| Multi-touch gestures | Single-pointer alternative required | Sequential taps, menu options, mode switches | Toolbar buttons or settings menu for multi-touch actions |
| Path-based gestures (draw shape) | Alternative input method required 2.5.1 | Button selection, voice input, keyboard shortcuts | Provide pattern picker or command palette |
| Long press | Not required but recommended alternative | Menu button, right-click, dedicated action button | Show "..." menu button for context actions |
| Swipe navigation (carousel) | Previous/Next buttons required | Arrow buttons, pagination dots, keyboard arrows | Visible navigation controls always present |
Example: Accessible drag-and-drop with alternatives
<!-- List item with both drag and button-based reordering -->
<li role="listitem" aria-label="Task: Review pull request"
draggable="true">
<span class="drag-handle" aria-label="Drag to reorder">⋮⋮</span>
<span>Review pull request</span>
<!-- Alternative to dragging -->
<div class="reorder-controls" role="group" aria-label="Reorder controls">
<button aria-label="Move up">↑</button>
<button aria-label="Move down">↓</button>
</div>
</li>
Gesture Best Practices: Always provide at least one pointer-independent alternative (WCAG
2.5.1). Ensure alternatives are discoverable without requiring gesture knowledge. Test with assistive
technologies like switch controls and voice access.
4. Orientation and Zoom Handling
| Feature | WCAG Requirement | Implementation | Exceptions |
|---|---|---|---|
| Orientation support | Content not restricted to single orientation 1.3.4 AA | Support both portrait and landscape; use CSS media queries | Essential: piano app, bank check, projector slides |
| Zoom and scaling | Allow up to 200% zoom without loss of content 1.4.4 AA | <meta name="viewport" content="width=device-width, initial-scale=1"> |
Never use user-scalable=no or maximum-scale=1 |
| Text resize (400%) | Text scales to 400% without horizontal scroll 1.4.10 AA | Use responsive design, relative units (rem, em), fluid layouts | Images of text, captions (but must reflow on zoom) |
| Reflow at 320px | Content reflows without 2D scrolling at 320 CSS pixels 1.4.10 AA | Mobile-first design, avoid fixed widths, use flexbox/grid | Data tables, maps, diagrams, toolbars (horizontal scroll OK) |
| Orientation change | Content must not be lost on rotation | Test form data persistence, modal positions, focus retention | None - always preserve state |
Example: Proper viewport and orientation CSS
<!-- Correct viewport meta tag -->
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1">
<!-- NEVER do this (blocks zoom) -->
<!-- <meta name="viewport" content="maximum-scale=1, user-scalable=no"> -->
<!-- CSS for orientation support -->
@media (orientation: portrait) {
.toolbar {
flex-direction: column;
}
}
@media (orientation: landscape) {
.toolbar {
flex-direction: row;
}
}
/* Responsive font sizing with safe minimums */
body {
font-size: clamp(16px, 1rem + 0.5vw, 20px);
}
Common Zoom Mistakes: Disabling zoom via viewport meta, using
position: fixed with
pixel widths (causes overflow at zoom), using viewport units (vw/vh) for font sizes without clamp(), and
forgetting to test horizontal scrolling at 200%+ zoom levels.
5. Mobile Focus Management
| Focus Scenario | Mobile Consideration | Implementation | Testing Method |
|---|---|---|---|
| Virtual keyboard appearance | Scroll input into view, maintain focus visibility | element.scrollIntoView({ behavior: 'smooth', block: 'center' }) |
Test with on-screen keyboard on iOS/Android |
| Modal dialogs | Trap focus, prevent body scroll, return focus on close | Use inert attribute on background or focus trap library |
Test with screen reader swipe navigation |
| Dynamic content insertion | Move focus to new content or announce with ARIA live | Set focus to first interactive element or heading in new content | Verify screen reader announces insertion |
| Touch vs keyboard focus | Different focus indicators for touch and keyboard | Use :focus-visible to show focus only for keyboard |
Test with external keyboard on mobile device |
| Focus order in landscape | Maintain logical reading order across orientations | Use flexbox order or grid with careful tabindex management | Rotate device and verify tab order consistency |
| Bottom sheets / drawers | Focus first focusable element when opened | Programmatic focus on open, restore on close | Screen reader navigation and keyboard tab order |
Example: Mobile keyboard and focus management
// Handle virtual keyboard appearance
const input = document.getElementById('mobile-search');
input.addEventListener('focus', () => {
// Scroll element into view with padding for keyboard
setTimeout(() => {
input.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest'
});
}, 300); // Delay for keyboard animation
});
// Mobile modal with focus trap
class MobileModal {
open() {
this.previousFocus = document.activeElement;
this.modal.showModal(); // Native focus trap
// Focus first interactive element
const firstFocusable = this.modal.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
firstFocusable?.focus();
// Prevent body scroll on mobile
document.body.style.overflow = 'hidden';
}
close() {
this.modal.close();
document.body.style.overflow = '';
this.previousFocus?.focus(); // Restore focus
}
}
Mobile Focus Tips: Use
:focus-visible to show focus rings only for keyboard
navigation (not touch). Test with external keyboard on tablets. Ensure focus indicators are at least 3:1
contrast ratio against adjacent colors (WCAG 2.4.13).
6. Voice Control (Voice Access, Voice Control) Integration
| Voice Control Feature | Platform | Accessibility Requirement | Implementation |
|---|---|---|---|
| Voice Access (Android) | Android 11+ | Visible labels match accessible names | Ensure aria-label matches visible text or use aria-labelledby |
| Voice Control (iOS) | iOS 13+ | Interactive elements have accessible names | All tappable elements must have text or aria-label |
| Click by number | Both platforms | Elements numbered for voice activation | Proper semantic markup ensures numbering works |
| Click by name | Both platforms | Visible label text must be in accessible name 2.5.3 | Start aria-label with visible text: "Delete product X" |
| Show labels/numbers | Overlay mode | All interactive elements discoverable | Avoid pointer-events: none on focusable elements |
| Custom commands | Platform-specific | Document available voice commands | Provide help text for complex interactions |
Example: Voice control accessible button labeling
<!-- CORRECT: Visible label matches accessible name -->
<button aria-label="Delete product: Wireless Mouse">
Delete
</button>
<!-- Voice command: "Click Delete" works! -->
<!-- INCORRECT: Accessible name doesn't include visible text -->
<button aria-label="Remove item from cart">
Delete
</button>
<!-- Voice command: "Click Delete" FAILS -->
<!-- BEST: Use aria-labelledby for compound labels -->
<button aria-labelledby="delete-label product-name">
<span id="delete-label">Delete</span>
</button>
<span id="product-name" class="sr-only">Wireless Mouse</span>
Voice Control Label Rule (WCAG 2.5.3): When visible text labels exist, the accessible name MUST
start with the visible text. Example: Button shows "Search" → aria-label can be "Search products" but NOT "Find
products". This ensures voice commands like "Click Search" work reliably.
Mobile and Touch Accessibility Quick Reference
- Touch Targets: Minimum 24×24px (WCAG 2.2), recommended 48×48px with 8px spacing
- Screen Readers: Test with VoiceOver (iOS) and TalkBack (Android); keep labels concise
- Gestures: Provide pointer-independent alternatives for swipe, drag, pinch (WCAG 2.5.1, 2.5.7)
- Orientation: Support both portrait/landscape; never restrict to single orientation (WCAG 1.3.4)
- Zoom: Allow 200% zoom minimum; never use
user-scalable=noormaximum-scale=1 - Reflow: Content must reflow at 320px width without horizontal scrolling (WCAG 1.4.10)
- Focus: Use
:focus-visiblefor keyboard-only indicators; manage virtual keyboard with scrollIntoView - Voice Control: Visible labels must start accessible names (WCAG 2.5.3); test "Click [label]" commands
- Testing: Use real devices with screen readers, external keyboards, voice control, and switch access
- Key Tools: iOS VoiceOver, Android TalkBack, Voice Access, Chrome DevTools device mode, Xcode Accessibility Inspector