
WCAG 2.5.8 Target Size (Minimum): Complete Implementation Guide
WCAG 2.5.8 Target Size (Minimum) is a new Level AA success criterion introduced in WCAG 2.2. It requires that clickable and tappable targets be at least 24Γ24 CSS pixels, with specific exceptions for inline targets and user-controlled elements.
This is a Level AA requirement, meaning compliance is legally required under ADA, Section 508, and the European Accessibility Act (EAA) which came into force June 28, 2025.
What Does WCAG 2.5.8 Require?
Official Definition:
The size of the target for pointer inputs is at least 24 by 24 CSS pixels, except where:
- Spacing: The target offset is at least 24 CSS pixels to every adjacent target
- Inline: The target is in a sentence or block of text
- User Agent Control: The size of the target is determined by the user agent and is not modified by the author
- Essential: A particular presentation of the target is essential or is legally required
In Plain English: Buttons, links, form fields, and other interactive elements must be at least 24Γ24 CSS pixels in size, or have at least 24 pixels of spacing around them.
Key Points:
- β Minimum size: 24Γ24 CSS pixels (not physical pixels)
- β Alternative: Smaller targets OK if 24px spacing around them
- β Applies to: All pointer input targets (touch, mouse, stylus)
- β Exceptions: Inline links, native controls, essential presentations
Why This Matters
Real-World Impact:
Problem Scenario
User on mobile phone tries to tap "Delete" button
β Button is only 16Γ16 pixels
β User's finger is 10-12mm wide (about 40-45 CSS pixels)
β User accidentally taps "Cancel" instead
β Frustration, errors, abandonment
Who This Helps:
- Mobile users: Average adult finger pad is 10-12mm (40-48 CSS pixels)
- Users with motor impairments: Tremors, limited dexterity
- Older adults: Reduced fine motor control
- Users with Parkinson's: Difficulty with precision tapping
- Anyone using a device while moving: In a car, on a train, walking
Statistics:
- 70% of mobile web traffic worldwide
- 40-48 CSS pixels: Average adult finger pad size
- 57 CSS pixels: Recommended by Apple Human Interface Guidelines
- 48Γ48 dp (density-independent pixels): Google Material Design recommendation
Common Failure Patterns
β Failure 1: Icon-Only Buttons Too Small
<button class="icon-btn">
<svg width="16" height="16">
<path d="..."/> <!-- Delete icon -->
</svg>
</button>
<style>
.icon-btn {
width: 16px; /* Too small! */
height: 16px;
padding: 0;
border: none;
}
</style>
Result: 16Γ16px target fails WCAG 2.5.8.
β Failure 2: Close Buttons in Modals
<div class="modal">
<button class="close" aria-label="Close">Γ</button>
<div class="modal-content">...</div>
</div>
<style>
.close {
position: absolute;
top: 10px;
right: 10px;
width: 20px; /* Too small! */
height: 20px;
padding: 0;
font-size: 20px;
}
</style>
Result: 20Γ20px target fails, and it's a critical interaction.
β Failure 3: Form Input Checkboxes
<label>
<input type="checkbox"> <!-- Default browser size: 13-16px -->
I agree to the terms
</label>
<style>
input[type="checkbox"] {
/* No styling = browser default (13-16px) */
/* Fails WCAG 2.5.8! */
}
</style>
Result: Default checkbox ~13-16px, needs to be 24Γ24px.
β Failure 4: Pagination Links
<nav aria-label="Pagination">
<a href="?page=1">1</a>
<a href="?page=2">2</a>
<a href="?page=3">3</a>
<a href="?page=4">4</a>
</nav>
<style>
nav a {
display: inline-block;
width: 20px; /* Too small! */
height: 20px;
margin: 0 2px; /* Not enough spacing! */
text-align: center;
line-height: 20px;
}
</style>
Result: 20Γ20px targets with only 2px spacing fails.
β Solution 1: Minimum 24Γ24px Size
The simplest approach:
/* All interactive elements minimum 24Γ24px */
button,
a,
input[type="submit"],
input[type="button"],
input[type="checkbox"],
input[type="radio"],
select,
.interactive {
min-width: 24px;
min-height: 24px;
}
/* Use padding to reach minimum if content is smaller */
.icon-btn {
padding: 4px; /* Creates 24Γ24px even if icon is 16Γ16px */
min-width: 24px;
min-height: 24px;
}
Example:
<button class="icon-btn" aria-label="Delete">
<svg width="16" height="16">
<path d="..."/> <!-- Delete icon -->
</svg>
</button>
<style>
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
min-height: 24px;
padding: 4px; /* 4px padding + 16px icon = 24px total */
border: 1px solid #ccc;
background: white;
cursor: pointer;
}
</style>
β Solution 2: 24px Spacing Alternative
If you need smaller visual targets, add spacing:
<nav class="pagination" aria-label="Pagination">
<a href="?page=1">1</a>
<a href="?page=2">2</a>
<a href="?page=3">3</a>
</nav>
<style>
.pagination a {
display: inline-block;
width: 20px; /* Visual size can be smaller */
height: 20px;
margin: 2px 14px; /* Top/bottom: 2px, Left/right: 14px */
/* Total offset: 2+20+2 = 24px vertical */
/* 14+20+14 = 48px horizontal */
text-align: center;
line-height: 20px;
}
</style>
How Spacing Works:
- Each target must have 24px clearance to adjacent targets
- Measure from edge of one target to edge of next target
- This creates effectively larger hit areas
β Solution 3: Custom Checkbox/Radio Styling
<label class="checkbox-label">
<input type="checkbox" class="visually-hidden">
<span class="checkbox-custom"></span>
I agree to the terms
</label>
<style>
/* Hide native checkbox but keep accessible */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Custom checkbox that meets 24Γ24px requirement */
.checkbox-custom {
display: inline-block;
width: 24px;
height: 24px;
border: 2px solid #333;
border-radius: 4px;
background: white;
vertical-align: middle;
margin-right: 8px;
position: relative;
cursor: pointer;
}
/* Checked state */
.visually-hidden:checked + .checkbox-custom::after {
content: '';
position: absolute;
left: 7px;
top: 3px;
width: 6px;
height: 12px;
border: solid #0066cc;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
}
/* Focus indicator */
.visually-hidden:focus + .checkbox-custom {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Hover state */
.checkbox-label:hover .checkbox-custom {
border-color: #0066cc;
}
</style>
β Solution 4: Modal Close Buttons
<div class="modal" role="dialog" aria-modal="true">
<button class="modal-close" aria-label="Close dialog">
<svg width="16" height="16" aria-hidden="true">
<path d="M4 4 L12 12 M12 4 L4 12" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
<div class="modal-content">
<h2>Modal Title</h2>
<p>Modal content...</p>
</div>
</div>
<style>
.modal-close {
position: absolute;
top: 8px;
right: 8px;
min-width: 24px;
min-height: 24px;
padding: 4px; /* Creates room for 16px icon */
border: none;
background: transparent;
cursor: pointer;
border-radius: 4px;
}
/* Visible hover/focus states */
.modal-close:hover,
.modal-close:focus {
background: rgba(0, 0, 0, 0.1);
outline: 2px solid #0066cc;
outline-offset: 2px;
}
</style>
β Solution 5: Mobile-First Responsive Targets
/* Base size for mobile (touch-first) */
.btn {
min-width: 48px; /* iOS Human Interface Guidelines */
min-height: 48px;
padding: 12px 16px;
font-size: 16px;
}
/* Can be slightly smaller on desktop (mouse precision) */
@media (hover: hover) and (pointer: fine) {
.btn {
min-width: 32px; /* Still above 24px minimum */
min-height: 32px;
padding: 8px 12px;
font-size: 14px;
}
}
/* Ensure always meets 24Γ24px minimum */
@media (max-width: 768px) {
.btn,
a,
button,
input[type="submit"] {
min-width: 44px; /* Apple's recommended minimum */
min-height: 44px;
}
}
Exception: Inline Links
Inline links (within sentences) are exempt:
<p>
Read our <a href="/privacy">privacy policy</a> and
<a href="/terms">terms of service</a> for more information.
</p>
<style>
/* These inline links don't need to be 24Γ24px */
p a {
/* Normal text size is fine */
color: #0066cc;
text-decoration: underline;
}
/* But increase tap area with padding is still helpful */
p a {
padding: 4px 0; /* Vertical padding helps without breaking flow */
display: inline-block;
}
</style>
Why: Inline links are part of text flow, making them 24px tall would disrupt readability.
Implementation Checklist
1. Audit All Interactive Elements
// Find all interactive elements and measure their size
function auditTargetSizes() {
const interactiveSelectors = [
'a[href]',
'button',
'input:not([type="hidden"])',
'select',
'textarea',
'[role="button"]',
'[role="link"]',
'[tabindex]:not([tabindex="-1"])',
'[onclick]'
];
const elements = document.querySelectorAll(interactiveSelectors.join(','));
const violations = [];
elements.forEach(el => {
const rect = el.getBoundingClientRect();
const computedStyle = window.getComputedStyle(el);
// Check if inline element (exempt)
const isInline = computedStyle.display === 'inline' &&
el.closest('p, li, td, th, dd, dt');
// Check size
if (!isInline && (rect.width < 24 || rect.height < 24)) {
// Check spacing to adjacent targets
const hasAdequateSpacing = checkSpacing(el);
if (!hasAdequateSpacing) {
violations.push({
element: el,
selector: getSelector(el),
width: Math.round(rect.width),
height: Math.round(rect.height),
text: el.textContent?.trim().substring(0, 30)
});
}
}
});
console.log(`Found ${violations.length} target size violations:`);
console.table(violations);
return violations;
}
function checkSpacing(el) {
const rect = el.getBoundingClientRect();
const allInteractive = document.querySelectorAll('a, button, input, select, [role="button"]');
for (let other of allInteractive) {
if (other === el) continue;
const otherRect = other.getBoundingClientRect();
// Calculate minimum distance between elements
const horizontalDistance = Math.min(
Math.abs(rect.right - otherRect.left),
Math.abs(otherRect.right - rect.left)
);
const verticalDistance = Math.min(
Math.abs(rect.bottom - otherRect.top),
Math.abs(otherRect.bottom - rect.top)
);
// Check if elements are adjacent (within 100px)
const areAdjacent = horizontalDistance < 100 || verticalDistance < 100;
if (areAdjacent) {
// Check if spacing is at least 24px
if (horizontalDistance < 24 || verticalDistance < 24) {
return false;
}
}
}
return true;
}
function getSelector(el) {
return el.tagName.toLowerCase() +
(el.id ? '#' + el.id : '') +
(el.className ? '.' + el.className.split(' ').join('.') : '');
}
// Run audit
auditTargetSizes();
2. Fix Common Elements
/* Global reset for minimum target sizes */
/* Buttons */
button,
[role="button"],
input[type="submit"],
input[type="button"],
input[type="reset"] {
min-width: 24px;
min-height: 24px;
padding: 8px 16px;
}
/* Links (except inline) */
a:not(p a):not(li a):not(td a) {
min-width: 24px;
min-height: 24px;
display: inline-block;
padding: 4px 8px;
}
/* Form controls */
input[type="checkbox"],
input[type="radio"] {
width: 24px;
height: 24px;
cursor: pointer;
}
select {
min-height: 24px;
padding: 4px 8px;
}
/* Icon buttons */
.icon-btn,
.btn-icon {
min-width: 24px;
min-height: 24px;
padding: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Close buttons */
.btn-close,
.modal-close,
[aria-label*="close"],
[aria-label*="Close"] {
min-width: 24px;
min-height: 24px;
padding: 4px;
}
3. Mobile-Specific Enhancements
/* Touch-friendly sizes for mobile */
@media (max-width: 768px) {
/* Increase to Apple's 44Γ44pt recommendation */
button,
a:not(p a),
input[type="submit"],
input[type="button"],
.btn {
min-width: 44px;
min-height: 44px;
padding: 12px 16px;
}
/* Form controls */
input[type="checkbox"],
input[type="radio"] {
width: 32px;
height: 32px;
}
/* Icon buttons */
.icon-btn {
min-width: 44px;
min-height: 44px;
padding: 12px;
}
/* Navigation links */
nav a {
min-height: 44px;
padding: 12px 16px;
}
}
Platform-Specific Guidelines
iOS (Apple Human Interface Guidelines)
Recommended minimum: 44Γ44 pt (points)
/* iOS-optimized targets */
@supports (-webkit-touch-callout: none) {
button,
a,
input[type="button"],
.interactive {
min-width: 44px;
min-height: 44px;
}
}
Android (Material Design)
Recommended minimum: 48Γ48 dp (density-independent pixels)
/* Android-optimized targets */
@media (pointer: coarse) {
button,
a,
input[type="button"],
.interactive {
min-width: 48px;
min-height: 48px;
}
}
Web (WCAG 2.5.8)
Legal minimum: 24Γ24 CSS pixels
/* WCAG 2.5.8 compliance */
button,
a,
input,
select,
.interactive {
min-width: 24px;
min-height: 24px;
}
Testing Procedures
Manual Testing
-
Use Browser DevTools:
// Highlight all targets and show dimensions document.querySelectorAll('a, button, input, select').forEach(el => { const rect = el.getBoundingClientRect(); el.style.outline = rect.width >= 24 && rect.height >= 24 ? '2px solid green' : '2px solid red'; el.setAttribute('data-size', `${Math.round(rect.width)}Γ${Math.round(rect.height)}`); }); -
Physical Testing on Mobile:
- Test on actual devices (not just emulators)
- Try tapping all interactive elements
- Note any mis-taps or difficulty
-
Accessibility Inspector:
- Chrome DevTools β Elements β Accessibility pane
- Check "Computed Properties" β Size
Automated Testing
// Automated 2.5.8 compliance test
function test258Compliance() {
const interactiveElements = document.querySelectorAll(
'a[href], button, input:not([type="hidden"]), select, textarea, [role="button"], [role="link"]'
);
const violations = [];
const warnings = [];
interactiveElements.forEach(el => {
const rect = el.getBoundingClientRect();
const computedStyle = window.getComputedStyle(el);
const isInline = computedStyle.display === 'inline' && el.closest('p, li, td, th');
if (isInline) {
// Inline targets are exempt
return;
}
const width = rect.width;
const height = rect.height;
if (width < 24 || height < 24) {
violations.push({
element: el,
selector: getSelector(el),
width: Math.round(width),
height: Math.round(height),
text: el.textContent?.trim().substring(0, 30)
});
} else if (width < 44 || height < 44) {
// Meets WCAG but below platform recommendations
warnings.push({
element: el,
selector: getSelector(el),
width: Math.round(width),
height: Math.round(height),
recommendation: 'Consider 44Γ44px for better mobile UX'
});
}
});
console.log(`\n===== WCAG 2.5.8 Target Size Test =====`);
console.log(`Total interactive elements: ${interactiveElements.length}`);
console.log(`Violations (< 24Γ24px): ${violations.length}`);
console.log(`Warnings (< 44Γ44px): ${warnings.length}`);
if (violations.length > 0) {
console.log(`\nπ΄ VIOLATIONS:`);
console.table(violations);
}
if (warnings.length > 0) {
console.log(`\nβ οΈ WARNINGS:`);
console.table(warnings);
}
if (violations.length === 0) {
console.log(`\nβ
All targets meet WCAG 2.5.8 (24Γ24px minimum)`);
}
return { violations, warnings };
}
// Run test
test258Compliance();
Common Mistakes to Avoid
β Mistake 1: Only Testing on Desktop
/* This looks fine on desktop with mouse */
button {
width: 20px;
height: 20px;
}
Problem: Works with precise mouse cursor but fails on touch screens.
β Fix: Always test on actual mobile devices.
β Mistake 2: Forgetting About Padding
/* Visual size is 20Γ20, but clickable area is larger */
button {
width: 20px;
height: 20px;
padding: 10px; /* Total clickable area: 40Γ40px β
*/
}
Remember: Padding counts toward target size!
β Mistake 3: Assuming Icons Are Tappable
<button>
<svg width="16" height="16">...</svg>
</button>
Problem: SVG is 16px, but is the button's hit area 24px?
β Fix: Ensure button (not just icon) is 24Γ24px:
button {
min-width: 24px;
min-height: 24px;
padding: 4px; /* Creates space around 16px icon */
}
β Mistake 4: Tight Spacing Between Targets
<button>Prev</button><button>Next</button>
Problem: No spacing between buttons makes them hard to tap independently.
β Fix: Add margin:
button {
margin: 0 4px; /* Creates 8px gap between buttons */
}
Real-World Examples
β Good Example: GitHub Mobile
GitHub's mobile interface uses 44Γ48px tap targets:
/* Simplified GitHub mobile navigation */
.nav-item {
min-height: 48px;
padding: 12px 16px;
display: flex;
align-items: center;
}
β Good Example: Google Material Design
Material Design buttons meet 48Γ48dp minimum:
.mdc-button {
min-width: 64px;
min-height: 48px; /* Exceeds 24px requirement */
padding: 0 16px;
}
β Bad Example: Small Icon Buttons
Common failure in admin panels:
<button class="icon-delete">ποΈ</button>
<button class="icon-edit">βοΈ</button>
<style>
.icon-delete,
.icon-edit {
width: 16px; /* Too small! */
height: 16px;
font-size: 12px;
}
</style>
Framework-Specific Implementation
Tailwind CSS
<!-- Tailwind utility classes for target sizing -->
<button class="min-w-[24px] min-h-[24px] p-2">
Icon
</button>
<!-- Mobile-first approach -->
<button class="min-h-[44px] md:min-h-[32px] px-4 py-2">
Button
</button>
<!-- Custom Tailwind config -->
// tailwind.config.js
module.exports = {
theme: {
extend: {
spacing: {
'target-min': '24px',
'target-touch': '44px',
}
}
}
}
Bootstrap
// Override Bootstrap button sizing
.btn {
min-width: 24px;
min-height: 24px;
padding: 0.5rem 1rem;
}
.btn-sm {
min-width: 24px; // Ensure small buttons still meet minimum
min-height: 24px;
padding: 0.25rem 0.5rem;
}
// Custom form controls
.form-check-input {
width: 24px;
height: 24px;
}
React
// Accessible button component with minimum target size
const Button = ({ children, icon, ...props }) => {
return (
<button
style={{
minWidth: '24px',
minHeight: '24px',
padding: icon ? '4px' : '8px 16px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
}}
{...props}
>
{icon && <span style={{ width: '16px', height: '16px' }}>{icon}</span>}
{children}
</button>
);
};
How AllAccessible Helps
AllAccessible automatically detects and fixes target size violations:
Automatic Detection:
- Scans all interactive elements on your pages
- Measures actual rendered size (including padding)
- Identifies targets below 24Γ24px minimum
- Checks spacing between adjacent targets
Automatic Fixes:
- Injects CSS to ensure minimum 24Γ24px sizing
- Adds appropriate padding without breaking layouts
- Handles icon buttons, checkboxes, and custom controls
- Preserves visual design while ensuring compliance
Real-Time Monitoring:
- Continuous testing across all pages
- Alerts when new violations are introduced
- Mobile-specific testing and fixes
- Responsive design adjustments
Start Free Trial - Fix target size issues automatically - starting at $10/month.
Related Resources
π Master WCAG 2.2: Complete WCAG 2.2 Compliance Guide
Related Success Criteria:
- 2.5.5 Target Size (Enhanced) - Level AAA (44Γ44px minimum)
- 2.5.7 Dragging Movements - Pointer input alternatives
- 2.1.1 Keyboard - Keyboard operability
Platform Guides:
- Mobile Accessibility Best Practices
- Touch Target Sizing for iOS
- Android Material Design Accessibility
Summary
WCAG 2.5.8 Target Size (Minimum) Requirements:
- β Level AA (legally required since EAA enforcement June 28, 2025)
- β Minimum 24Γ24 CSS pixels for all interactive targets
- β Alternative: 24px spacing to adjacent targets
- β Exceptions: Inline links, native controls, essential presentations
Quick Implementation:
button, a, input, select {
min-width: 24px;
min-height: 24px;
}
/* Better: mobile-first approach */
@media (max-width: 768px) {
button, a, input, select {
min-width: 44px;
min-height: 44px;
}
}
Test by: Using the automated test script above or manually measuring all interactive elements on mobile devices.
Need help fixing target size issues across your entire site? AllAccessible automatically detects and fixes violations - starting at $10/month.