Touches & Gestures
There's no onclick and no :hover in React Native — mobile screens
respond to touch, not a mouse. React Native provides a small family of
components purpose-built for handling touch: the classic Touchable* components and
the newer, more flexible Pressable. This page goes deeper than the brief mention of
TouchableOpacity from the Core Components page and covers the full touch-handling
toolkit you'll actually reach for.
TouchableOpacity in Depth
TouchableOpacity wraps its children and dims them (reduces opacity) while pressed,
giving simple, automatic visual feedback for any tap. Its most important props:
import React from 'react';
import { TouchableOpacity, Text, StyleSheet, Alert } from 'react-native';
export default function SaveButton() {
return (
<TouchableOpacity
style={styles.button}
activeOpacity={0.6}
onPress={() => Alert.alert('Saved!')}
onLongPress={() => Alert.alert('Long press detected')}
>
<Text style={styles.buttonText}>Save</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
button: { backgroundColor: '#2196f3', padding: 12, borderRadius: 8 },
buttonText: { color: '#ffffff', textAlign: 'center', fontWeight: 'bold' },
});
| Prop | Purpose |
|---|---|
onPress | Called when the user taps and releases inside the component |
activeOpacity | Opacity value (0 to 1) applied while pressed — the default is 0.2, lower means a more visible dimming effect |
onLongPress | Called after the user holds the press down for a longer duration, without releasing early |
Pressable: The Newer, More Flexible API
Pressable is the more modern, general-purpose replacement for the various
Touchable* components. Its biggest advantage: the style prop can be a
function that receives the current press state, letting you style the pressed
state directly — something TouchableOpacity can't easily do on its own (it only
offers the fixed opacity-dimming effect).
import React from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';
export default function CustomButton() {
return (
<Pressable
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed,
]}
onPress={() => console.log('Pressed!')}
>
<Text style={styles.buttonText}>Tap Me</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#4caf50',
paddingVertical: 12,
paddingHorizontal: 20,
borderRadius: 8,
},
buttonPressed: {
backgroundColor: '#357a38', // darker shade while actively pressed
transform: [{ scale: 0.97 }], // slight shrink for tactile feedback
},
buttonText: { color: '#ffffff', fontWeight: 'bold', textAlign: 'center' },
});
Here, pressed is a boolean React Native provides automatically to the style
function — true for as long as the finger is down on the component. Because it's
plain JavaScript, you have full control: change background color, add a border, scale it down
slightly — anything a style object can express.
onPressIn / onPressOut: Granular Press Lifecycle Control
For cases where you need to react to the press starting and ending separately from the final
"tap completed" event, both TouchableOpacity and Pressable support
onPressIn and onPressOut:
<Pressable
onPressIn={() => console.log('Finger touched down')}
onPressOut={() => console.log('Finger lifted')}
onPress={() => console.log('Full tap completed')}
>
<Text>Hold me</Text>
</Pressable>
onPressIn fires the instant a touch begins, and onPressOut fires the
instant it ends — regardless of whether it turns into a completed onPress tap or
the user drags their finger away and cancels it. This is useful for things like starting/stopping
an animation or a sound effect in sync with the physical press itself.
Disabling a Touchable Component
Both TouchableOpacity and Pressable accept a disabled
prop, which stops onPress (and related callbacks) from firing. Setting
disabled alone does not change how the component looks, though —
you're responsible for visually communicating the disabled state yourself:
<Pressable
disabled={!isFormValid}
style={({ pressed }) => [
styles.button,
!isFormValid && styles.buttonDisabled, // grey it out when disabled
pressed && styles.buttonPressed,
]}
onPress={submitForm}
>
<Text>Submit</Text>
</Pressable>
const styles = StyleSheet.create({
button: { backgroundColor: '#2196f3', padding: 12, borderRadius: 8 },
buttonDisabled: { backgroundColor: '#cccccc' },
buttonPressed: { opacity: 0.7 },
});
disabled is a very common mistake — the button will silently stop responding to taps, but keep looking fully active, confusing users into thinking the app is broken.Beyond Taps: react-native-gesture-handler
Everything above covers simple taps and presses, which is most of what typical apps need. For
richer gestures — swipe-to-dismiss, pinch-to-zoom, drag-and-drop, pan gestures — the community
standard library is react-native-gesture-handler. It's a separate package (not part
of core React Native) that provides native-driven gesture recognizers far more capable than the
basic touch components on this page. It's commonly installed alongside navigation libraries like
@react-navigation, since many navigation transitions (like swipe-back) rely on it
under the hood. This is awareness-level information for now — advanced gesture handling is a
deep topic on its own, well beyond simple taps.
Full Worked Example: A "Like" Button
Putting several of these ideas together — a small, self-contained "like" button built with
Pressable that toggles its own color and icon when tapped:
import React, { useState } from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<Pressable
style={({ pressed }) => [
styles.button,
liked && styles.buttonLiked,
pressed && styles.buttonPressed,
]}
onPress={() => setLiked(!liked)}
>
<Text style={styles.icon}>{liked ? '♥' : '♡'}</Text>
<Text style={[styles.label, liked && styles.labelLiked]}>
{liked ? 'Liked' : 'Like'}
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 14,
borderRadius: 20,
borderWidth: 1,
borderColor: '#cccccc',
},
buttonLiked: { borderColor: '#e91e63', backgroundColor: '#fde7ee' },
buttonPressed: { opacity: 0.7 },
icon: { fontSize: 18, color: '#e91e63', marginRight: 6 },
label: { fontSize: 14, color: '#555555' },
labelLiked: { color: '#e91e63', fontWeight: 'bold' },
});
Each tap flips the liked boolean in state, which in turn selects a different set of
styles from the array and swaps the heart glyph — the same array-of-styles and conditional
rendering techniques from earlier pages, now driven entirely by a touch event.