Skip to main content
Back to Blog
WCAG 2.2

WCAG 2.5.8 Target Size (Minimum): Complete Implementation Guide

Master WCAG 2.5.8 Target Size (Minimum) - Level AA criterion. Learn how to implement 24x24px minimum touch targets for mobile accessibility with practical code examples and testing procedures.

AllAccessible
14 min read
WCAG 2.2Mobile AccessibilityTouch TargetsLevel AAImplementation
WCAG 2.5.8 Target Size (Minimum): Complete Implementation Guide

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

  1. 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)}`);
    });
    
  2. Physical Testing on Mobile:

    • Test on actual devices (not just emulators)
    • Try tapping all interactive elements
    • Note any mis-taps or difficulty
  3. 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:

Platform Guides:

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.

Share this article