Design mobile interfaces with adequate touch target sizes, proper spacing, and accessibility features for users with motor impairments — including WCAG 2.2 Target Size requirements.
Design mobile interfaces with adequate touch target sizes, proper spacing, and accessibility features for users with motor impairments — including WCAG 2.2 Target Size requirements.
WCAG 2.2 introduced Target Size Minimum (2.5.8, Level AA): interactive targets must be at least 24x24 CSS pixels, OR have adequate spacing so that a 24px circle centered on the target does not intersect any other target or the circle of another target below 24px. The enhanced level 2.5.5 (AAA) requires 44x44px targets with no spacing requirement. Apple's Human Interface Guidelines and Google's Material Design both recommend 44x44px as the minimum for touch targets — align with the enhanced AAA standard as a practical target for all new designs.
Targets under 24x24px that fail the spacing test are a WCAG AA violation in 2022+. The most common violations:
1/* FAILING: 16x16px icon button — below minimum */
2.social-icon {
3 width: 16px;
4 height: 16px;
5}
6
7/* PASSING: Use padding to expand touch target without changing visual size */
8.social-icon {
9 padding: 14px; /* visual icon: 16px + 28px padding = 44px touch target */
10 display: inline-flex;
11 align-items: center;
12 justify-content: center;
13}
14.social-icon svg {
15 width: 16px;
16 height: 16px;
17 flex-shrink: 0;
18}Three CSS patterns for achieving 44x44px touch targets while maintaining the visual design:
Pattern 1: Padding expansion (preferred for buttons and links):
1.nav-icon-button {
2 /* Visual size: 24px icon */
3 /* Touch target: 44px via padding */
4 padding: 10px;
5 /* Ensure the button itself is at least 44x44 */
6 min-width: 44px;
7 min-height: 44px;
8 display: inline-flex;
9 align-items: center;
10 justify-content: center;
11}Pattern 2: Pseudo-element expansion (for links where padding creates layout issues):
1.inline-link {
2 position: relative;
3}
4
5.inline-link::before {
6 content: '';
7 position: absolute;
8 inset: -10px; /* Expands touch target 10px in all directions */
9}Pattern 3: min-height on touch devices (for list items and table rows):
1@media (pointer: coarse) {
2 /* 'coarse' pointer = touch screen */
3 .list-item {
4 min-height: 44px;
5 display: flex;
6 align-items: center;
7 padding-block: 10px;
8 }
9
10 .checkbox-label {
11 min-height: 44px;
12 display: flex;
13 align-items: center;
14 gap: 12px;
15 }
16}When interactive elements are close together (icon button groups, tag lists, inline link clusters), spacing prevents accidental activation:
1/* Icon button group with adequate spacing */
2.button-group {
3 display: flex;
4 gap: 8px; /* Minimum 8px between touch targets */
5}
6
7.button-group .icon-button {
8 min-width: 44px;
9 min-height: 44px;
10 padding: 10px;
11}
12
13/* Tag list with touch-friendly spacing */
14.tag-list {
15 display: flex;
16 flex-wrap: wrap;
17 gap: 8px;
18}
19
20.tag {
21 min-height: 32px;
22 padding: 4px 12px;
23 /* At 32px height the tag is below 44px but above 24px;
24 the 8px gap satisfies the WCAG 2.5.8 spacing requirement */
25}
26
27/* On touch devices, ensure full 44px */
28@media (pointer: coarse) {
29 .tag {
30 min-height: 44px;
31 padding: 10px 16px;
32 }
33}Swipe gestures (swipe to delete, swipe to reveal actions, pull to refresh) are inaccessible to users who cannot perform precise directional swipes. WCAG 2.5.1 (Pointer Gestures, Level A) requires that all multipoint or path-based gestures have a single-pointer alternative.
1// Swipe-to-delete with accessible button alternative
2function SwipeableListItem({ item, onDelete }: ListItemProps) {
3 const [showActions, setShowActions] = useState(false);
4
5 return (
6 <li className="swipeable-item">
7 {/* Swipeable content area */}
8 <div
9 className="item-content"
10 onTouchStart={handleTouchStart}
11 onTouchMove={handleTouchMove}
12 onTouchEnd={handleTouchEnd}
13 >
14 {item.name}
15
16 {/* Always-visible action button — the accessible alternative */}
17 <button
18 aria-label={`More actions for ${item.name}`}
19 onClick={() => setShowActions(!showActions)}
20 className="item-actions-trigger"
21 >
22 <MoreIcon aria-hidden />
23 </button>
24 </div>
25
26 {/* Action panel — appears on swipe OR tap of more-actions button */}
27 {showActions && (
28 <div role="menu" aria-label={`Actions for ${item.name}`}>
29 <button role="menuitem" onClick={() => onDelete(item.id)}>
30 Delete
31 </button>
32 <button role="menuitem" onClick={() => onArchive(item.id)}>
33 Archive
34 </button>
35 </div>
36 )}
37 </li>
38 );
39}Pull to refresh needs a button alternative:
1<button
2 onClick={triggerRefresh}
3 aria-label="Refresh feed"
4 className="refresh-button"
5>
6 <RefreshIcon aria-hidden />
7 Refresh
8</button>iOS VoiceOver quickstart:
1Enable: Settings > Accessibility > VoiceOver > On
2Tap: moves focus and announces element
3Double tap: activates the focused element
4Swipe right: next element
5Swipe left: previous element
6Two-finger swipe up: read page from current position
7Three-finger swipe up/down: scroll
8Three-finger double tap: toggle speech
9
10Key test: can you complete your critical user flow using only
11 VoiceOver gestures without sighted assistance?Android TalkBack quickstart:
1Enable: Settings > Accessibility > TalkBack > On
2Tap: select element and announce
3Double tap: activate selected element
4Swipe right: next element
5Swipe left: previous element
6Swipe down then right: open TalkBack menuMobile accessibility Playwright test with device emulation:
1// tests/mobile/mobile-a11y.spec.ts
2import { test, expect } from '@playwright/test';
3
4test.use({ viewport: { width: 390, height: 844 } }); // iPhone 14 Pro
5
6test('touch targets meet minimum size', async ({ page }) => {
7 await page.goto('/');
8 const buttons = page.locator('button, a, [role="button"]');
9 const count = await buttons.count();
10
11 for (let i = 0; i < count; i++) {
12 const btn = buttons.nth(i);
13 const box = await btn.boundingBox();
14 if (box) {
15 expect(box.height, `Button at index ${i} height`).toBeGreaterThanOrEqual(24);
16 expect(box.width, `Button at index ${i} width`).toBeGreaterThanOrEqual(24);
17 }
18 }
19});Install the Accessibility Insights for Android or iOS app and run a touch target size check on your mobile interface. Identify all elements below 44x44px and add CSS padding expansion to bring them into compliance.
Your progress is saved in this browser, on this device only.