---
title: Increase tap target size and spacing for mobile usability
description: >-
  Make interactive elements at least 48 × 48 px on mobile so that customers can
  tap accurately, and meet the WCAG 2.2 minimum target size.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/increase-tap-target-size
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/increase-tap-target-size.md
api_name: liquid
---

# Increase tap target size and spacing for mobile usability

Small tap targets force customers to zoom in and tap carefully, which increases error rates. Appropriately sized touch targets, at a minimum of 48 × 48 px, make a storefront easier to use on a touchscreen and help you meet accessibility requirements.

***

## Why

Customers interact with touchscreens using fingers, which are less precise than mouse cursors. When tap targets are too small or too close together, customers make errors and take longer to complete tasks.

Small tap targets create specific problems:

* **Mis-taps**: customers accidentally tap the wrong elements, and then have to undo the mistake and try again.
* **Extra interactions**: every correction is another tap that the page has to handle, so a mis-tap-prone layout does more work than it needs to.
* **Zoom requirements**: customers have to zoom in to tap accurately, which interrupts the task.
* **Accessibility barriers**: customers with motor impairments struggle with small targets.
* **Abandonment**: frustration leads to task abandonment and site exits.

A fingertip is much less precise than a cursor. Ten millimeters is about 38 CSS px at the reference resolution of 96 px per inch, and the major platform and accessibility guidelines all round up from a figure in that range.

There are three commonly cited target sizes, and they aren't interchangeable:

* **WCAG 2.2 SC 2.5.8 Target Size (Minimum)**: 24 × 24 CSS px, at Level AA. This is the criterion that most conformance targets require. It has exceptions for spacing, inline targets, targets whose size is essential, and targets with an equivalent alternative elsewhere on the page.
* **WCAG 2.1 SC 2.5.5 Target Size**: 44 × 44 CSS px, at Level AAA. Apple's Human Interface Guidelines use the same 44 × 44 pt figure.
* **Material Design**: 48 × 48 dp, which is a platform guideline rather than a conformance requirement.

The 48 × 48 px recommendation on this page satisfies all three.

***

## How

### Set minimum tap target sizes

Use a minimum of 48 × 48 px for interactive elements:

```css
/* Buttons */
button,
.button,
input[type="submit"],
input[type="button"] {
  min-height: 48px;
  min-width: 48px;
  padding: 12px 24px;
  font-size: 16px;
}


/* Links in navigation */
.site-nav a {
  min-height: 48px;
  padding: 12px 16px;
  display: flex;
  align-items: center;
}


/* Icon buttons */
.icon-button {
  min-height: 48px;
  min-width: 48px;
  padding: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}


/* Form inputs */
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="number"],
select {
  min-height: 48px;
  padding: 12px;
  font-size: 16px;
}


/* Checkboxes and radio buttons */
input[type="checkbox"],
input[type="radio"] {
  min-height: 24px;
  min-width: 24px;
  margin: 12px; /* Separates neighboring targets. Margin isn't part of the hit area. */
}


/* Enlarge the hit area itself by making the label the target */
.checkbox-field label {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
}
```

### Add adequate spacing between targets

Use a minimum of 8 px spacing between interactive elements:

```css
/* Button groups */
.button-group {
  display: flex;
  gap: 12px; /* Space between buttons */
  flex-wrap: wrap;
}


/* Navigation links */
.site-nav {
  display: flex;
  gap: 8px; /* Minimum spacing */
}


/* Product grid */
.product-grid {
  display: grid;
  gap: 16px; /* Comfortable spacing for tap targets */
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}


/* Form fields */
.form-field {
  margin-bottom: 20px; /* Space between fields */
}
```

### Increase clickable area with padding

Expand tap area beyond visible element:

```css
/* Small icon with a large tap area */
.close-button {
  position: relative;
  width: 24px;
  height: 24px;
}


.close-button::before {
  content: '';
  position: absolute;
  top: -12px;
  left: -12px;
  right: -12px;
  bottom: -12px;
  /* Creates a 48x48px tap area around a 24x24px icon */
}


/* Alternative: use padding */
.icon-link {
  display: inline-flex;
  padding: 12px; /* Adds 12px on each side, so a 24px icon becomes a 48x48px target */
}


.icon-link svg {
  width: 24px;
  height: 24px;
}
```

### Make entire card or row tappable

Expand clickable area to container:

```liquid
{%- comment -%}
  Make the entire product card tappable.
{%- endcomment -%}


<a href="{{ product.url }}" class="product-card">
  <img src="{{ product.featured_image | image_url: width: 400 }}" alt="{{ product.title }}">
  <h3>{{ product.title }}</h3>
  <p>{{ product.price | money }}</p>
</a>


<style>
  .product-card {
    display: block;
    padding: 16px;
    text-decoration: none;
    color: inherit;
    /* The entire card is tappable */
  }


  .product-card:hover,
  .product-card:focus {
    background: #f5f5f5;
  }
</style>
```

Stretched link pattern:

```liquid
{%- comment -%}
  Make the container tappable while keeping semantic HTML.
{%- endcomment -%}


<div class="blog-card">
  <img src="{{ article.image | image_url: width: 400 }}" alt="{{ article.title }}">
  <h3>
    <a href="{{ article.url }}" class="stretched-link">{{ article.title }}</a>
  </h3>
  <p>{{ article.excerpt }}</p>
</div>


<style>
  .blog-card {
    position: relative;
    padding: 16px;
  }


  .stretched-link::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    /* Expands the link to cover the entire card */
  }
</style>
```

### Navigation and menu items

Mobile-friendly navigation:

```css
/* Mobile menu items */
.mobile-nav a {
  display: block;
  min-height: 48px;
  padding: 12px 20px;
  font-size: 16px;
  text-decoration: none;
  color: #333;
  border-bottom: 1px solid #eee;
}


.mobile-nav a:active {
  background: #f5f5f5;
}


/* Hamburger menu button */
.menu-toggle {
  min-width: 48px;
  min-height: 48px;
  padding: 12px;
  background: transparent;
  border: none;
  cursor: pointer;
}
```

### Product variant selectors

Large, tappable variant options:

```liquid
{%- comment -%}
  Color swatches with adequate size and spacing.
{%- endcomment -%}


<div class="variant-selector">
  <label>Color</label>
  <div class="color-swatches">
    {% for option in product.options_with_values %}
      {% if option.name == 'Color' %}
        {% for value in option.values %}
          <input
            type="radio"
            name="color"
            id="color-{{ value | handle }}"
            value="{{ value }}"
            class="swatch-input"
          >
          <label for="color-{{ value | handle }}" class="swatch-label">
            <span class="swatch" style="background-color: {{ value | downcase }}"></span>
            <span class="swatch-name">{{ value }}</span>
          </label>
        {% endfor %}
      {% endif %}
    {% endfor %}
  </div>
</div>


<style>
  .color-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
  }


  .swatch-input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }


  .swatch-label {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 60px;
    min-height: 60px;
    padding: 8px;
    cursor: pointer;
    border: 2px solid transparent;
    border-radius: 4px;
  }


  .swatch {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 2px solid #ddd;
  }


  .swatch-name {
    font-size: 12px;
    margin-top: 4px;
  }


  .swatch-input:checked + .swatch-label {
    border-color: #0066cc;
  }


  /* The input is visually hidden, so the label carries the focus indicator */
  .swatch-input:focus-visible + .swatch-label {
    outline: 2px solid #0066cc;
    outline-offset: 2px;
  }
</style>
```

***

## Examples

### Mobile navigation with proper tap targets

```liquid
<nav class="mobile-nav">
  <button class="menu-toggle" aria-label="Toggle menu">
    <svg width="24" height="24" viewBox="0 0 24 24">
      <path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
    </svg>
  </button>


  <ul class="nav-menu">
    {% for link in linklists.main-menu.links %}
      <li>
        <a href="{{ link.url }}">{{ link.title }}</a>
      </li>
    {% endfor %}
  </ul>
</nav>


<style>
  .menu-toggle {
    min-width: 48px;
    min-height: 48px;
    padding: 12px;
    background: transparent;
    border: none;
    display: flex;
    align-items: center;
    justify-content: center;
  }


  .menu-toggle:active {
    background: rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }


  .nav-menu {
    list-style: none;
    margin: 0;
    padding: 0;
  }


  .nav-menu a {
    display: block;
    min-height: 48px;
    padding: 14px 20px;
    font-size: 16px;
    color: #333;
    text-decoration: none;
    border-bottom: 1px solid #eee;
  }


  .nav-menu a:active {
    background: #f5f5f5;
  }
</style>
```

### Product quantity selector with large tap targets

```liquid
<div class="quantity-selector">
  <label for="quantity">Quantity</label>
  <div class="quantity-controls">
    <button type="button" class="quantity-button" onclick="decreaseQuantity()">
      <svg width="16" height="16" viewBox="0 0 16 16">
        <path d="M2 8h12" stroke="currentColor" stroke-width="2"/>
      </svg>
    </button>


    <input
      type="number"
      id="quantity"
      name="quantity"
      value="1"
      min="1"
      class="quantity-input"
    >


    <button type="button" class="quantity-button" onclick="increaseQuantity()">
      <svg width="16" height="16" viewBox="0 0 16 16">
        <path d="M8 2v12M2 8h12" stroke="currentColor" stroke-width="2"/>
      </svg>
    </button>
  </div>
</div>


<style>
  .quantity-controls {
    display: flex;
    align-items: center;
    gap: 8px;
  }


  .quantity-button {
    min-width: 48px;
    min-height: 48px;
    padding: 12px;
    background: #f5f5f5;
    border: 1px solid #ddd;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
  }


  .quantity-button:active {
    background: #e5e5e5;
  }


  .quantity-input {
    min-height: 48px;
    width: 60px;
    padding: 12px;
    font-size: 16px;
    text-align: center;
    border: 1px solid #ddd;
    border-radius: 4px;
  }
</style>


<script>
  function decreaseQuantity() {
    const input = document.getElementById('quantity');
    const value = parseInt(input.value);
    if (value > 1) {
      input.value = value - 1;
    }
  }


  function increaseQuantity() {
    const input = document.getElementById('quantity');
    input.value = parseInt(input.value) + 1;
  }
</script>
```

***

## Testing

1. **Touch target testing**: use Chrome DevTools Device Mode. Enable **Show rulers** to measure tap targets, verify that all interactive elements are at least 48 × 48 px, and check spacing between targets (at least 8 px).

2. **Real device testing**: test on actual mobile devices. Try tapping all interactive elements, verify that no mis-taps occur, and check that tapping feels comfortable.

3. **Accessibility testing**: confirm that every target meets WCAG 2.2 SC 2.5.8 (24 × 24 CSS px, Level AA), or one of its exceptions. If you're targeting Level AAA, then check SC 2.5.5 (44 × 44 CSS px) as well. Verify that keyboard navigation reaches every target and that each one has a visible focus indicator.

4. **Automated testing**: run the Lighthouse accessibility category and check the **Touch targets do not have sufficient size or spacing** (`target-size`) audit. Lighthouse 12.0.0 replaced the older SEO `tap-targets` audit with this accessibility audit, so an older report shows a different audit name.

***

## References

* [Material Design: touch targets](https://m2.material.io/design/usability/accessibility.html#layout-and-typography)
* [Apple Human Interface Guidelines: touch targets](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/)
* [WCAG 2.2: Target Size (Minimum), Level AA](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html)
* [WCAG 2.1: Target Size, Level AAA](https://www.w3.org/WAI/WCAG21/Understanding/target-size.html)
* [Nielsen Norman Group: touch target sizes](https://www.nngroup.com/articles/touch-target-size/)

***
