How tap targets reach past the button you can see.
Every platform publishes a minimum size for things people tap, and developers read those numbers as a minimum size for things people see. So a 24-point close icon gets drawn at 44 points to pass, the toolbar swells, and the design that looked right in the mock looks clumsy in the build. The guidelines never asked for that. They ask for a touchable area of a certain size, and the touchable area can be much larger than the drawing.
What the three numbers actually say
Apple’s Human Interface Guidelines put it plainly: a button needs a hit region of at least 44×44 pt, rising to 60×60 pt in visionOS, where people select things with their eyes. Hit region is the operative phrase. The same page, describing macOS image buttons, notes that a button’s edges define its clickable area “even when they aren’t visible.”
Android’s accessibility guidance asks for touch targets of at least 48×48 dp, separated by 8 dp or more. Google’s own example is the standard icon: drawn at 24 dp, with padding that makes up the full 48. Because dp scales with screen density, 48 dp comes out at roughly 9 mm on any phone, which is about the width of a fingertip.
The web’s number comes from WCAG 2.2. Success criterion 2.5.8, at level AA, asks for targets of at least 24×24 CSS pixels. The stricter 2.5.5, at AAA, asks for 44×44. The AA rule has an escape hatch that matters for dense interfaces: a smaller target still passes if a 24-pixel circle centred on it doesn’t touch any other target or another small target’s circle. In other words, spacing can stand in for size.
Read together, the three rules describe the same thing from different angles. A finger is a blunt instrument that lands somewhere near where the person aimed, and each tappable thing needs enough room around it that the near-miss still lands on it and not on its neighbour. None of that requires the visible shape to grow.
Why the drawing should stay small
Visual size carries meaning. A large filled button says “this is the main thing on the screen.” A small glyph in a corner says “this is here when you need it.” Inflating every secondary control to 44 points flattens that hierarchy, so the dismiss icon competes with the primary action and the eye has nowhere to start. The hit region is where you give secondary controls their generosity, without changing what they say.
It also matters for alignment. An icon whose visible edge sits on your 16-point margin looks aligned. If you pad the visible shape out to 44 points, the icon’s glyph floats inward and the left edge of the screen develops a ragged column. Keep the glyph on the grid and let its invisible target hang over the margin.
How each platform grows the target
Flutter. Material widgets already do this. IconButton and friends size
themselves to kMinInteractiveDimension, which is 48. Checkboxes, radios and
switches follow ThemeData.materialTapTargetSize, which defaults to
padded (a 48×48 target) on phones and shrinkWrap on desktop. The
trap is setting shrinkWrap to tighten a layout: it does tighten it, by deleting
the touchable margin around every affected control. For custom widgets, put the
GestureDetector on a box of at least 48×48 and centre the small drawing inside
it, and set behavior: HitTestBehavior.opaque so the empty part of the box
counts.
Jetpack Compose. Material components pad themselves to 48 dp. For your own clickable
composables, Modifier.minimumInteractiveComponentSize() reserves 48 dp of layout
space around a smaller element. Put it before any size modifier, or the fixed size wins.
SwiftUI. Give the label a frame of at least 44×44 and add
.contentShape(Rectangle()). Without the content shape, taps only register on the
parts of the view that actually draw something, so the padding you added for touch may not
be touchable at all.
UIKit. A UIButton receives touches inside its bounds. You can make the
bounds bigger and inset the image, or override point(inside:with:) to accept
points within an enlarged rectangle while the frame stays the size of the art.
The web. Give the control position: relative and add a pseudo-element
with position: absolute and a negative inset. The pseudo-element is
part of the button, so a tap on it is a tap on the button, and it changes nothing about
layout. A @media (pointer: coarse) query lets you widen it only for touch.
Where enlarged targets go wrong
Invisible areas can overlap, and when they do, whichever element sits on top in hit-testing order wins. A row of four icons spaced 32 points apart, each with a 44-point target, has 12 points of overlap between every pair. A tap that lands slightly left of the third icon can go to the second, and the person has no way to see why. The fix is to split the gap: grow each target into the space between neighbours, never past the halfway point. If that leaves a target under the minimum, the icons are too close together, and that’s a layout problem rather than a hit-testing one.
The other failure is the edge of a scroll view or a card with clipping. A target that hangs outside its parent’s bounds will often be ignored there, because most frameworks hit-test the parent first and stop when the point falls outside it. If a corner button feels dead along one side, check whether the container is cutting off its target.
Checking it instead of trusting it
Hit regions are invisible by definition, so review them deliberately. The quickest method is a debug flag that tints every target with a translucent colour. Overlaps and slivers show up immediately, and the screenshot makes a good thing to attach to a design review.
There is tooling too. Google’s Accessibility Scanner app flags touch targets under 48 dp on a
running Android build. On iOS, UI tests can call performAccessibilityAudit(),
whose audit types include a hit-region check. Flutter’s widget tests ship
androidTapTargetGuideline and iOSTapTargetGuideline, which you can
assert with meetsGuideline so an undersized control fails CI instead of a
thumb.
Most “this app feels fiddly” complaints trace back to targets someone drew, not targets someone measured. It’s one of the cheapest polish fixes there is, because nothing visible changes. If your listing screenshots show the polish too, they’re worth getting right.