Lesson 10 of 26
Pressable and Touch Handling
The modern touch-response primitive in React Native: onPress and onLongPress, style-as-a-function press feedback, and hitSlop for bigger touch targets.
A phone has no mouse. There's no hover state to sniff before a click, no right-click context menu waiting in the wings — touch is the entire input model, and React Native's job is to turn "a finger touched this region of the screen" into an event your component can react to.
Where Pressable fits
Early React Native shipped a family of components for this: TouchableOpacity
(dims on press), TouchableHighlight (adds a highlight underlay),
TouchableWithoutFeedback (no built-in visual feedback at all), and later
TouchableNativeFeedback (Android-only ripple). You will still run into all
of these in real, shipping codebases — they aren't broken, and migrating a
large app off them isn't always worth doing on its own. But for new code,
Pressable is the current recommended default. Instead of a different
component per feedback style, it's one component with a flexible API that
covers all of those cases and more.
import { Pressable, Text } from "react-native";
function LikeButton({ onLike }: { onLike: () => void }) {
return (
<Pressable onPress={onLike}>
<Text>Like</Text>
</Pressable>
);
}onPress and onLongPress
onPress fires on a normal tap-and-release within the element's bounds.
onLongPress fires when the user presses and holds past a threshold —
useful for a distinct "hold" action that shouldn't be triggered by an
ordinary tap.
<Pressable
onPress={() => console.log("opened")}
onLongPress={() => console.log("show context menu")}
>
<Text>Message bubble</Text>
</Pressable>These are two different gestures with two different intents. A single
onPress handler that tries to guess "was that a long press?" by measuring
time itself is exactly the kind of thing onLongPress exists so you never
have to write.
Press feedback: style as a function
This is the part of Pressable that genuinely replaces the old Touchable
family. Instead of a plain style object, style can be a function that
receives the current interaction state and returns the style to use:
<Pressable
onPress={onLike}
style={({ pressed }) => ({
opacity: pressed ? 0.5 : 1,
backgroundColor: pressed ? "#eee" : "#fff",
padding: 12,
borderRadius: 8,
})}
>
<Text>Like</Text>
</Pressable>pressed is true for exactly as long as a finger is down on the element.
There's no useState to wire up and no re-render to reason about by hand —
Pressable re-invokes the style function itself as the press state changes.
This one pattern reproduces TouchableOpacity's dimming, TouchableHighlight's
underlay, or any custom feedback you want, all from the same component.
style can also be a plain object if you don't need press-state-dependent
styling at all — the function form is only worth reaching for when the
style actually depends on pressed (or the other states the callback
receives, like hovered on platforms that support it).
hitSlop: a bigger touch target without a bigger element
Design and usability guidance for touch targets (roughly 44 points on iOS, 44dp on Android, both long-standing platform recommendations) doesn't always line up with how big an icon is supposed to look. A 16px trash-can icon is easy to see and easy to mis-tap.
hitSlop solves this by extending the touchable area without touching
the visual one:
<Pressable
onPress={onDelete}
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
>
<TrashIcon size={16} />
</Pressable>The icon still renders at 16px. But a touch landing up to 12 logical pixels
outside its edges in any direction still counts as a press on it. This
matters because it's easy to conflate "make it easier to tap" with "make it
bigger" — hitSlop is proof those are separable problems, and solving the
first doesn't require compromising a design that calls for a small icon.
hitSlop also accepts a single number as shorthand for equal slop on all
four sides: hitSlop={12}.
Putting it together
A realistic pressable list row combines several of these:
function ListRow({ label, onPress, onLongPress }: RowProps) {
return (
<Pressable
onPress={onPress}
onLongPress={onLongPress}
hitSlop={8}
style={({ pressed }) => [
styles.row,
pressed && styles.rowPressed,
]}
>
<Text style={styles.label}>{label}</Text>
</Pressable>
);
}Notice style here returns an array — React Native accepts arrays of
style objects (later entries win on conflicting properties), which is the
standard way to layer a conditional style on top of a base one, whether or
not you're inside the function form.
What to remember
Pressableis the current recommended default for touch handling; the olderTouchableOpacity/TouchableHighlight/TouchableWithoutFeedbackfamily still appears in real code but has been superseded.onPresshandles a normal tap;onLongPresshandles a distinct press-and-hold gesture — don't fake one by timing the other.style={({ pressed }) => ...}computes feedback styling from the live press state, with no extra local state needed.hitSlopenlarges the touchable area without changing the visual size — a small icon can still be easy to hit accurately.- Touch target sizing and visual sizing are separate concerns;
Pressable's API reflects that separation directly.
Check yourself
4 questions · pass 3/4 to unlock TextInput and Forms
1.Given the Touchable* family (TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback) still shows up in older codebases, what's the current recommended default for handling touch in new React Native code?
2.What does the function-as-style pattern
style={({ pressed }) => ...}let you do that a static style object can't?3.What problem does hitSlop solve?
4.Why might a component use both onPress and onLongPress?
4 left to answer