View contents
Introduction
Touch targets are the interactive areas users can tap, click, or touch to activate controls like buttons, links, and form inputs. WCAG 2.5.5 (Target Size Enhanced) and 2.5.8 (Target Size Minimum) establish minimum size requirements to ensure users can accurately activate controls without accidentally triggering adjacent elements.
For users with motor impairments, tremors, or limited fine motor control, small touch targets create significant barriers. Even users without disabilities benefit from larger targets—especially on mobile devices, in bright sunlight, or while multitasking.
What Are Touch Targets?
The Basic Concept
A touch target is the clickable/tappable area of an interactive element:
<!-- The button's touch target is its visible area plus padding -->
<button class="action-btn">
Submit Form
</button>
<style>
.action-btn {
/* Visual size */
min-width: 44px;
min-height: 44px;
padding: 12px 24px;
}
</style>
The touch target includes:
- The visible element (button, link, icon)
- Any padding that expands the clickable area
- The total area that responds to pointer events
Who Benefits
| User Type | Challenge | Solution |
|---|---|---|
| Motor impairments | Difficulty with precise movements | Larger targets reduce precision needed |
| Tremors (Parkinson’s) | Involuntary hand movements | More space prevents mis-taps |
| Arthritis | Limited finger dexterity | Bigger areas easier to hit |
| Low vision | Difficulty seeing small controls | Larger targets easier to locate |
| Elderly users | Reduced motor control | Generous sizing improves accuracy |
| Mobile users | Touch input challenges | Adequate targets for finger tapping |
WCAG Requirements
2.5.8 Target Size (Minimum) - Level AA
New in WCAG 2.2, this criterion requires:
The size of the target for pointer inputs is at least 24 by 24 CSS pixels, except when:
- Spacing: Undersized targets have enough spacing so a 24×24 circle could be placed on each target without overlap
- Equivalent: Another target for the same function is 24×24 or larger
- Inline: The target is in a sentence or block of text
- User Agent Control: The target size is determined by the browser
- Essential: The specific presentation is legally required or essential
2.5.5 Target Size (Enhanced) - Level AAA
For enhanced accessibility:
The size of the target for pointer inputs is at least 44 by 44 CSS pixels, except for the same exceptions as 2.5.8.
Why These Sizes?
| Size | Level | Rationale |
|---|---|---|
| 24×24 px | AA | Minimum for basic accessibility |
| 44×44 px | AAA | Based on Apple/iOS recommendations |
| 48×48 dp | Material Design recommendation |
Common Touch Target Problems
1. Targets Too Small
<!-- BAD: Icon button with no padding -->
<button class="icon-btn">
<svg width="16" height="16"><!-- icon --></svg>
</button>
<style>
.icon-btn {
padding: 0;
/* Total size: 16×16 - too small! */
}
</style>
2. Targets Too Close Together
<!-- BAD: Buttons without adequate spacing -->
<div class="button-group">
<button>Save</button>
<button>Cancel</button>
<button>Delete</button>
</div>
<style>
.button-group button {
margin: 2px; /* Not enough spacing! */
}
</style>
3. Clickable Area Smaller Than Visual
<!-- BAD: Large visual but small clickable area -->
<div class="card" onclick="handleClick()">
<img src="thumbnail.jpg" alt="Product">
<a href="/product" class="tiny-link">View Details</a>
</div>
<style>
.tiny-link {
font-size: 10px;
/* Link is tiny despite large card */
}
</style>
4. Hidden Touch Areas
/* BAD: Padding doesn't count if overflow is hidden */
.container {
overflow: hidden;
}
.container button {
padding: 20px;
margin: -10px; /* Padding cut off! */
}
Proper Touch Target Sizing
Minimum Sizing (Level AA)
/* Meets 24×24 minimum requirement */
.interactive-element {
min-width: 24px;
min-height: 24px;
}
/* Better: Add padding to small elements */
.icon-button {
padding: 8px;
/* If icon is 16×16, total becomes 32×32 */
}
Enhanced Sizing (Level AAA)
/* Meets 44×44 enhanced requirement */
.touch-target {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
Using Spacing Exception
/* Small targets with adequate spacing */
.nav-link {
padding: 4px 8px;
margin: 12px; /* Creates 24px gap between targets */
}
Measuring Touch Targets
What Counts as Target Size
- Clickable area - The area that responds to clicks/taps
- Visual bounds - What users see and aim for
- Hit testing area - Browser’s pointer detection zone
CSS Pixels vs Physical Pixels
Touch target requirements use CSS pixels, not device pixels:
/* 44 CSS pixels = 44px in your stylesheet */
/* On 2x Retina display = 88 physical pixels */
/* On 3x display = 132 physical pixels */
.button {
min-width: 44px; /* CSS pixels */
min-height: 44px; /* CSS pixels */
}
Testing Touch Targets
Quick Manual Test
- Use browser DevTools to measure element dimensions
- Check computed styles for width/height
- Verify clickable area matches visual area
- Test spacing between adjacent targets
What to Verify
- [ ] All interactive elements are at least 24×24 CSS pixels
- [ ] Touch targets have adequate spacing (no overlapping hit areas)
- [ ] Icon buttons have sufficient padding
- [ ] Link text has adequate clickable area
- [ ] Form inputs meet size requirements
Tools for Testing
- Browser DevTools - Measure element dimensions
- axe DevTools - Reports target size issues
- WAVE - Visual highlight of small targets
- Mobile device testing - Real-world touch testing
Design Recommendations
Beyond Minimum Requirements
| Context | Recommended Size |
|---|---|
| Primary actions | 48×48 px or larger |
| Navigation items | 44×44 px minimum |
| Form inputs | 44×44 px touch area |
| Secondary actions | 36×36 px minimum |
| Dense interfaces | 24×24 px with spacing |
Spacing Guidelines
/* Ensure 24px minimum spacing */
.button-group {
gap: 24px;
}
/* Or use margin */
.action-button {
margin: 12px; /* 12px × 2 = 24px between buttons */
}
Best Practices Summary
| Do | Don’t |
|---|---|
| Use min-width/min-height for sizing | Rely only on content to size targets |
| Add padding to small icons | Use tiny clickable areas |
| Provide spacing between targets | Cluster interactive elements |
| Test on actual touch devices | Only test with mouse |
| Consider all input methods | Design only for desktop |
Implementation
CSS Sizing Techniques
Basic Minimum Sizing
/* Level AA: 24×24 CSS pixels minimum */
.target-aa {
min-width: 24px;
min-height: 24px;
}
/* Level AAA: 44×44 CSS pixels minimum */
.target-aaa {
min-width: 44px;
min-height: 44px;
}
/* Google/Material recommendation: 48×48 dp */
.target-material {
min-width: 48px;
min-height: 48px;
}
Expanding Small Elements with Padding
When content is smaller than the required target size, use padding:
/* Icon is 16×16, need 44×44 target */
.icon-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px; /* (44 - 16) / 2 = 14px */
border: none;
background: transparent;
cursor: pointer;
}
.icon-button svg {
width: 16px;
height: 16px;
}
/* For 24×24 minimum with 16×16 icon */
.icon-button-aa {
padding: 4px; /* (24 - 16) / 2 = 4px */
}
Expanding Links Without Visual Change
Use pseudo-elements to expand clickable area without changing layout:
/* Link text remains visually small, but target is larger */
.expanded-link {
position: relative;
/* Visual styling unchanged */
}
.expanded-link::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
min-width: 44px;
min-height: 44px;
}
Using Transparent Borders
Expand clickable area while maintaining visual design:
.button-with-expanded-target {
/* Visual appearance */
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
/* Invisible border expands target */
border: 10px solid transparent;
background-clip: padding-box;
margin: -10px; /* Compensate for border */
}
Component Patterns
Icon Buttons
<!-- Proper icon button structure -->
<button
type="button"
class="icon-button"
aria-label="Close dialog"
>
<svg class="icon" aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</svg>
</button>
<style>
.icon-button {
/* Ensure minimum target size */
min-width: 44px;
min-height: 44px;
/* Center icon */
display: inline-flex;
align-items: center;
justify-content: center;
/* Reset button styles */
padding: 0;
border: none;
background: transparent;
cursor: pointer;
/* Focus and hover states */
border-radius: 50%;
transition: background-color 0.2s;
}
.icon-button:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.icon-button:focus-visible {
outline: 2px solid #007bff;
outline-offset: 2px;
}
.icon {
width: 24px;
height: 24px;
}
</style>
Navigation Links
<nav class="main-nav" aria-label="Main navigation">
<ul class="nav-list">
<li><a href="/" class="nav-link">Home</a></li>
<li><a href="/about" class="nav-link">About</a></li>
<li><a href="/services" class="nav-link">Services</a></li>
<li><a href="/contact" class="nav-link">Contact</a></li>
</ul>
</nav>
<style>
.nav-list {
display: flex;
gap: 8px;
list-style: none;
padding: 0;
margin: 0;
}
.nav-link {
/* Minimum target size */
display: flex;
align-items: center;
min-height: 44px;
padding: 0 16px;
/* Visual styling */
text-decoration: none;
color: #333;
border-radius: 4px;
transition: background-color 0.2s;
}
.nav-link:hover {
background-color: #f5f5f5;
}
.nav-link:focus-visible {
outline: 2px solid #007bff;
outline-offset: -2px;
}
</style>
Form Inputs
<div class="form-field">
<label for="email">Email address</label>
<input
type="email"
id="email"
class="form-input"
autocomplete="email"
>
</div>
<style>
.form-input {
/* Minimum target height */
min-height: 44px;
/* Full width for easier targeting */
width: 100%;
/* Padding for content and target size */
padding: 12px 16px;
/* Visual styling */
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px; /* Prevents zoom on iOS */
}
.form-input:focus {
outline: 2px solid #007bff;
outline-offset: -2px;
border-color: #007bff;
}
</style>
Checkboxes and Radio Buttons
<div class="checkbox-field">
<input type="checkbox" id="agree" class="checkbox-input">
<label for="agree" class="checkbox-label">
I agree to the terms and conditions
</label>
</div>
<style>
.checkbox-field {
display: flex;
align-items: flex-start;
gap: 12px;
}
/* Hide native checkbox, but keep accessible */
.checkbox-input {
/* Size the checkbox */
width: 24px;
height: 24px;
margin: 0;
/* Or hide and use custom styling */
appearance: none;
border: 2px solid #666;
border-radius: 4px;
cursor: pointer;
}
.checkbox-input:checked {
background-color: #007bff;
border-color: #007bff;
}
.checkbox-input:focus-visible {
outline: 2px solid #007bff;
outline-offset: 2px;
}
.checkbox-label {
/* Make entire label clickable */
min-height: 24px;
cursor: pointer;
line-height: 24px;
}
</style>
Spacing Exception Implementation
Understanding the Spacing Exception
WCAG 2.5.8 allows targets smaller than 24×24 if there’s sufficient spacing:
/* Small targets (16×16) with adequate spacing */
.small-target-with-spacing {
width: 16px;
height: 16px;
/* 8px margin on all sides = 16px gap */
/* Combined with element size: 16 + 8 + 8 = 32px center-to-center */
margin: 8px;
}
/* Alternatively, use gap in flex/grid */
.target-group {
display: flex;
gap: 24px; /* Ensures 24×24 circles don't overlap */
}
.target-group .small-target {
width: 16px;
height: 16px;
}
Visual Explanation
Target A (16×16) Target B (16×16)
████ ████
████ ████
◯ ← 24×24 circle ◯ ← 24×24 circle
With 24px gap, circles don't overlap = PASS
Practical Spacing Pattern
/* Pagination with small indicators */
.pagination {
display: flex;
align-items: center;
gap: 24px;
}
.pagination-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #ccc;
border: none;
cursor: pointer;
/* No need for min-width/height due to spacing */
}
.pagination-dot.active {
background: #007bff;
}
/* But make sure prev/next buttons meet size requirements */
.pagination-button {
min-width: 44px;
min-height: 44px;
}
Framework Implementations
React Component
// TouchTarget.tsx
import React, { forwardRef } from 'react';
import clsx from 'clsx';
import styles from './TouchTarget.module.css';
interface TouchTargetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
size?: 'aa' | 'aaa' | 'material';
variant?: 'icon' | 'text' | 'contained';
children: React.ReactNode;
}
export const TouchTarget = forwardRef<HTMLButtonElement, TouchTargetProps>(
({ size = 'aaa', variant = 'text', children, className, ...props }, ref) => {
return (
<button
ref={ref}
className={clsx(
styles.touchTarget,
styles[size],
styles[variant],
className
)}
{...props}
>
{children}
</button>
);
}
);
// TouchTarget.module.css
.touchTarget {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
cursor: pointer;
transition: background-color 0.2s;
}
.aa { min-width: 24px; min-height: 24px; }
.aaa { min-width: 44px; min-height: 44px; }
.material { min-width: 48px; min-height: 48px; }
.icon {
padding: 8px;
border-radius: 50%;
}
.text {
padding: 8px 16px;
border-radius: 4px;
}
.contained {
padding: 12px 24px;
background-color: #007bff;
color: white;
border-radius: 4px;
}
.touchTarget:hover { background-color: rgba(0, 0, 0, 0.04); }
.touchTarget:focus-visible {
outline: 2px solid #007bff;
outline-offset: 2px;
}
Vue 3 Component
<!-- TouchTarget.vue -->
<script setup lang="ts">
interface Props {
size?: 'aa' | 'aaa' | 'material'
variant?: 'icon' | 'text' | 'contained'
as?: 'button' | 'a'
}
const props = withDefaults(defineProps<Props>(), {
size: 'aaa',
variant: 'text',
as: 'button'
})
</script>
<template>
<component
:is="as"
:class="[
'touch-target',
`touch-target--${size}`,
`touch-target--${variant}`
]"
>
<slot />
</component>
</template>
<style scoped>
.touch-target {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
cursor: pointer;
text-decoration: none;
transition: background-color 0.2s;
}
.touch-target--aa {
min-width: 24px;
min-height: 24px;
}
.touch-target--aaa {
min-width: 44px;
min-height: 44px;
}
.touch-target--material {
min-width: 48px;
min-height: 48px;
}
.touch-target--icon {
padding: 8px;
border-radius: 50%;
}
.touch-target--text {
padding: 8px 16px;
border-radius: 4px;
}
.touch-target--contained {
padding: 12px 24px;
background-color: #007bff;
color: white;
border-radius: 4px;
}
.touch-target:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.touch-target--contained:hover {
background-color: #0056b3;
}
.touch-target:focus-visible {
outline: 2px solid #007bff;
outline-offset: 2px;
}
</style>
Tailwind CSS Utilities
/* tailwind.config.js */
module.exports = {
theme: {
extend: {
minWidth: {
'target-aa': '24px',
'target-aaa': '44px',
'target-material': '48px',
},
minHeight: {
'target-aa': '24px',
'target-aaa': '44px',
'target-material': '48px',
},
},
},
}
/* Usage in HTML */
<button class="min-w-target-aaa min-h-target-aaa flex items-center justify-center">
<svg class="w-6 h-6"><!-- icon --></svg>
</button>
Responsive Touch Targets
Mobile-First Approach
/* Larger targets on touch devices */
.interactive-element {
/* Mobile: larger targets */
min-width: 48px;
min-height: 48px;
padding: 12px;
}
/* Desktop: can use smaller targets with pointer */
@media (pointer: fine) {
.interactive-element {
min-width: 32px;
min-height: 32px;
padding: 6px;
}
}
/* Touch devices always get larger targets */
@media (pointer: coarse) {
.interactive-element {
min-width: 48px;
min-height: 48px;
padding: 12px;
}
}
Device-Agnostic Pattern
/* CSS custom properties for flexible sizing */
:root {
--target-size-min: 44px;
--target-padding: 10px;
}
@media (pointer: fine) {
:root {
--target-size-min: 32px;
--target-padding: 6px;
}
}
.touch-target {
min-width: var(--target-size-min);
min-height: var(--target-size-min);
padding: var(--target-padding);
}
Testing Touch Targets
Automated Testing
// Playwright test for touch targets
import { test, expect } from '@playwright/test';
test.describe('Touch Target Compliance', () => {
test('all buttons meet minimum size requirements', async ({ page }) => {
await page.goto('/');
const buttons = await page.locator('button').all();
for (const button of buttons) {
const box = await button.boundingBox();
// Check minimum 24×24 (Level AA)
expect(box.width).toBeGreaterThanOrEqual(24);
expect(box.height).toBeGreaterThanOrEqual(24);
}
});
test('interactive elements have adequate spacing', async ({ page }) => {
await page.goto('/');
const interactiveElements = await page.locator(
'button, a, input, select, textarea, [role="button"]'
).all();
// Check spacing between adjacent elements
for (let i = 0; i < interactiveElements.length - 1; i++) {
const box1 = await interactiveElements[i].boundingBox();
const box2 = await interactiveElements[i + 1].boundingBox();
// Calculate distance between centers
const center1 = { x: box1.x + box1.width / 2, y: box1.y + box1.height / 2 };
const center2 = { x: box2.x + box2.width / 2, y: box2.y + box2.height / 2 };
const distance = Math.sqrt(
Math.pow(center2.x - center1.x, 2) +
Math.pow(center2.y - center1.y, 2)
);
// If elements are close, they should be large enough or have spacing
if (distance < 48) {
expect(box1.width).toBeGreaterThanOrEqual(24);
expect(box1.height).toBeGreaterThanOrEqual(24);
}
}
});
});
Manual Testing Checklist
-
DevTools Measurement:
- Open browser DevTools
- Select interactive element
- Check computed width and height
- Verify meets 24×24 minimum
-
Visual Spacing Check:
- Look for clustered interactive elements
- Ensure adequate visual spacing
- Test with touch on mobile device
-
Click Area Verification:
- Click at edges of elements
- Verify click registers
- Check padding contributes to target
Common Mistakes and Fixes
Mistake 1: Relying on Content Size
/* WRONG: Target size depends on text length */
.button {
padding: 4px 8px;
/* Short text = tiny button */
}
/* CORRECT: Enforce minimum size */
.button {
min-width: 44px;
min-height: 44px;
padding: 8px 16px;
}
Mistake 2: Overflow Cutting Target Area
/* WRONG: Overflow hidden cuts padding */
.container {
overflow: hidden;
}
.container .button {
padding: 20px;
margin: -10px; /* Padding cut off! */
}
/* CORRECT: Use proper spacing */
.container {
padding: 10px;
}
.container .button {
padding: 20px;
}
Mistake 3: Transform Affecting Hit Area
/* WRONG: Scale doesn't increase hit area */
.icon-button {
transform: scale(2);
/* Visual is larger, but hit area unchanged */
}
/* CORRECT: Use actual sizing */
.icon-button {
min-width: 44px;
min-height: 44px;
}
Summary Checklist
- [ ] All interactive elements are at least 24×24 CSS pixels (Level AA)
- [ ] Primary actions are 44×44 CSS pixels or larger (Level AAA)
- [ ] Icon buttons have adequate padding around icons
- [ ] Adjacent targets have minimum 24px spacing (or larger size)
- [ ] Form inputs have minimum 44px touch height
- [ ] Checkboxes and radio buttons have adequate target size
- [ ] Navigation links have sufficient clickable area
- [ ] Tested on actual touch devices
- [ ] Responsive sizing for different input methods
- [ ] Focus states visible on all targets
Related Articles
- Form Accessibility Guide - Form input sizing
- WCAG Compliance Hub - All accessibility evaluators