Hit Area

Expand the clickable box of a control by a fixed amount, without moving anything on screen.

Ships with the spark styles (@epilot/spark-ui/styles), so there is no install step. hit-area-* adds an absolutely positioned ::before with negative insets, so the control claims presses outside its painted box while its layout, size and position stay exactly as they were.

Unlike touch-target, hit-area-* applies on every pointer type and expands by an amount you choose rather than up to a minimum. Use it to close a dead zone between controls that are already large enough, and touch-target to bring a small control up to the 44px floor on touch.

Usage

ClassEffect
hit-area-<n>expand by spacing step n on all sides
hit-area-x-<n> / hit-area-y-<n>expand on the horizontal / vertical axis
hit-area-t-<n>hit-area-l-<n>expand one side (top, right, bottom, left)
hit-area-[10px]arbitrary value, any axis or side variant
hit-areathe pseudo-element with no expansion
hit-area-debugpaint the claimed box while you tune it
<button className="hit-area-x-1 size-8 rounded-md">
  <Delete />
</button>

Values follow the spacing scale and resolve through --spacing, so an expansion tracks [data-scaling] the same way the surrounding layout does. Fractional steps work (hit-area-x-0.5 claims 2px per side at 100%).

Reach for a single side or axis rather than all four. A dead zone is usually directional: rows leave vertical gaps, toolbars leave horizontal ones, and expanding the axis that has no gap is how you end up stealing from a neighbor.

hit-area-debug paints the claimed box while you tune it, dashed blue at rest and green on hover, as in the demo above. It is a development aid: take it off before shipping.

Notes

  • Split the gap, do not claim all of it. Two adjacent controls that each expand by the full gap both claim the same pixels, and the one later in the DOM wins the overlap. The tap boundary then sits against one control's edge instead of between the two. Expanding each side by half the gap puts the boundary at the midpoint, which is why app-nav uses hit-area-x-0.5 into its 4px gap rather than hit-area-x-1.
  • The expansion inherits pointer-events. A control marked pointer-events-none gets an inert expansion, so an ancestor that owns the press keeps it.
  • It can add scrollable overflow. The ::before is absolutely positioned and reaches outside the box, so inside a scroll container it can extend the scrollable area. Reserve it with margin or padding on the control.
  • ::before is taken. The expansion uses ::before, and so do checkbox, radio and switch for their own overlays. touch-target uses ::after for this reason, and the two can be combined on one element.
  • Verify by probing, not by reading the classes. elementFromPoint walking outward from a control's center is the only way to confirm what it really owns, including which control wins an overlap.

Hit area utilities by Kian Bazarjani.

On this page