Mobile and Touch Accessibility
1. Touch Target Size 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 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 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