In React Native's Pressable, how do hitSlop and pressRetentionOffset differ, and what are their defaults?
answer
- start area versus drift area
- HitRect, then PressRect
- slop: no default
- retention: 20, 20, 20, bottom 30
- parent bounds and z-order cap it
basics
~20 shitSlop widens where a press can start and has no default. pressRetentionOffset, default top/left/right 20 and bottom 30, sets how far beyond that area an active press may drift before onPressOut fires and the tap is lost.
solid answer
~40 s`hitSlop` extends the view's bounds into a **HitRect**, the area where a press may begin; it has no default, so without it a press must start on the view itself. `pressRetentionOffset` extends the HitRect further into a **PressRect**: once pressed, the finger can move anywhere inside it and the press stays active. It defaults to `{top: 20, left: 20, right: 20, bottom: 30}`. Leaving the PressRect fires `onPressOut`, and releasing outside it fires no `onPress`. Both accept a `Rect` or a single number, and neither changes layout. The docs add two limits: the touch area never extends past the parent view's bounds, and where areas overlap the sibling on top wins. That is why a large `hitSlop` on keys packed edge to edge in a calculator grid does not help.
code
tsx · 15 linesimport {Pressable, Text} from 'react-native';
type SignToggleProps = {onToggle: () => void};
export function SignToggle({onToggle}: SignToggleProps) {
return (
<Pressable
onPress={onToggle}
hitSlop={12}
pressRetentionOffset={{top: 8, left: 8, right: 8, bottom: 16}}
style={({pressed}) => ({padding: 4, opacity: pressed ? 0.5 : 1})}>
<Text>+/-</Text>
</Pressable>
);
}go deeper
Recall that hitSlop is about where a press can start and pressRetentionOffset is about how far an active press can drift, and that neither changes layout.
Explain the HitRect and PressRect nesting, the retention default of 20 with 30 at the bottom, and the observable behaviour on leaving: onPressOut, pressed false, no onPress on release.
Diagnose wrong-key taps using the two documented limits, parent bounds and sibling z-order, and tune retention for risky keys instead of adding more hitSlop.
Treat touch geometry as a shared design-system setting: standardise slop and retention per control size so every screen fails and forgives in the same way.
## Three rectangles around one view React Native's `Pressable` reasons about a touch with three nested areas. The docs name two of them, `HitRect` and `PressRect`, and draw them around the element's own bounds: - **Visual rect**: the laid-out bounds of the `Pressable`'s `View`. - **HitRect**: the visual rect grown by **`hitSlop`**. A press can **start** anywhere inside it. - **PressRect**: the HitRect grown further by **`pressRetentionOffset`**. Once a press has started, the finger may **wander** anywhere inside it and the press stays active. Neither prop changes layout. The key keeps its size and position; only the touch geometry grows. Both accept either a `Rect` (`{top, left, right, bottom}`, any side optional) or a single number applied to all four sides. ## hitSlop: where a press can begin `hitSlop` has **no default**, so without it a press must start inside the view's own bounds. Setting it lets a touch just outside the view still start a press. That is how a small icon or a narrow key can be easier to hit without redesigning the layout. Two limits from the docs matter in practice: 1. **The touch area never extends past the parent view's bounds.** A key at the edge of its row container gains nothing beyond that container's edge. 2. **When two touch areas overlap, the sibling on top wins.** In a calculator grid with no gaps, a big `hitSlop` on every key does not make any key easier to hit, because each key's slop overlaps its neighbour and z-order decides. So `hitSlop` helps where there is free space around a target: a small `+/-` toggle in the display header, an icon in a toolbar, the gap between keys. ## pressRetentionOffset: how far a press may drift `pressRetentionOffset` defaults to **`{top: 20, left: 20, right: 20, bottom: 30}`**, applied beyond the HitRect. Its job is forgiveness while the finger is down: - While the finger stays inside the PressRect, the press remains active and `pressed` stays `true`. - When it leaves, `onPressOut` fires, `pressed` becomes `false`, and the long-press timer is cancelled. - If it comes back before lifting, the press reactivates and `onPressIn` fires again. - Lifting outside the PressRect fires no `onPress`. That is the "slide off to cancel" behaviour users expect. The larger bottom default reflects how a thumb tends to roll downward while pressing. Setting `pressRetentionOffset={0}` makes a key very strict: any drift off its HitRect cancels the press, which feels broken on a phone. ## Side by side | | `hitSlop` | `pressRetentionOffset` | |---|---|---| | Question it answers | where can a press start? | how far can an active press drift? | | Measured from | the view's own bounds | the HitRect (view plus `hitSlop`) | | Default | none | top 20, left 20, right 20, bottom 30 | | Leaving it | the press never starts | `onPressOut`; no `onPress` on release | | Changes layout? | no | no | ## Applying it to a calculator keypad 1. Lay the keys out with real spacing. Touch geometry cannot rescue keys that are visually too small; sizing guidelines for touch targets are an accessibility topic of their own. 2. Use `hitSlop` on small, isolated controls, not on keys packed edge to edge. 3. Leave `pressRetentionOffset` at its default unless you have a reason. Shrink it when accidental commits on slide-off are a problem, for example a key next to `=`. Grow it for large drag-friendly surfaces. 4. Remember that the retention area is measured beyond the HitRect, so a large `hitSlop` also pushes the PressRect outward. ## Debugging touch areas Both props live on `Pressable` itself, and the same `hitSlop` prop exists on `View` and the Touchables, so the behaviour is consistent across components. When a tap lands on the wrong key, check in this order: whether the neighbour is later in the tree (it sits on top), whether the slop reaches past the parent's bounds, and whether a parent is taking the touch first.
- Why does adding a large hitSlop to every key in a tightly packed React Native keypad not help?Each key's extended area overlaps its neighbours, and where touch areas overlap the sibling on top wins, so the touch still lands on whichever key is later in the tree. `hitSlop` also cannot extend past the parent's bounds. It only helps where there is free space around a control.
- What happens to pressed-state styling when the finger leaves the PressRect and comes back?Leaving fires `onPressOut`, so a function-valued `style` sees `pressed: false` and the key looks released. Coming back reactivates the press: `onPressIn` fires again and the pressed style returns. Lifting inside then fires `onPress`, so the visual state always tells the user whether a release will count.
saying these in an interview costs you the question
- hitSlop makes the Pressable's layout box larger and pushes siblings away.
- pressRetentionOffset controls where a press is allowed to start.
- pressRetentionOffset defaults to zero, so any slide off the view cancels the press.
- hitSlop can extend a touch area past its parent view's bounds.
- A larger hitSlop always wins over a neighbouring key's own area.