TouchableOpacity is the most commonly used touchable component in React Native. It responds to touches by decreasing the opacity of the wrapped view, providing subtle visual feedback without layout changes.
Usage
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
function App() {
return (
<TouchableOpacity
onPress={() => console.log('Pressed!')}
activeOpacity={0.7}
style={styles.button}
>
<Text style={styles.text}>Press Me</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 8,
alignItems: 'center',
},
text: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
});
Props
Determines what the opacity of the wrapped view should be when touch is active. A value between 0 and 1.<TouchableOpacity activeOpacity={0.5}>
<Text>50% opacity on press</Text>
</TouchableOpacity>
onPress
(event: GestureResponderEvent) => void
Called when the touch is released, but not if cancelled (e.g., by a scroll that steals the responder lock).<TouchableOpacity onPress={() => alert('Tapped!')}>
<Text>Tap me</Text>
</TouchableOpacity>
onPressIn
(event: GestureResponderEvent) => void
Called when a touch is engaged, immediately when the finger touches down.
onPressOut
(event: GestureResponderEvent) => void
Called when the touch is released or cancelled (finger lifted or moved away).
onLongPress
(event: GestureResponderEvent) => void
Called when the user holds down the press for more than delayLongPress milliseconds.<TouchableOpacity
onPress={() => console.log('Pressed')}
onLongPress={() => console.log('Long pressed')}
delayLongPress={1000}
>
<Text>Press or hold me</Text>
</TouchableOpacity>
If true, disables all interactions for this component.<TouchableOpacity disabled={isLoading}>
<Text>{isLoading ? 'Loading...' : 'Submit'}</Text>
</TouchableOpacity>
Delay in milliseconds from the start of the touch before onPressIn is called.
Delay in milliseconds from the release of the touch before onPressOut is called.
Delay in milliseconds from onPressIn before onLongPress is called.
Extends the touchable area without changing the visual layout. Useful for making small buttons easier to tap.<TouchableOpacity
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
>
<Text style={{ fontSize: 12 }}>Small text</Text>
</TouchableOpacity>
Defines how far the touch can move off the button before deactivating it.<TouchableOpacity
pressRetentionOffset={{ top: 20, left: 20, bottom: 20, right: 20 }}
>
<Text>Stays active even if finger moves slightly</Text>
</TouchableOpacity>
Style applied to the Animated.View wrapper.
Used to locate this view in end-to-end tests.
TouchableOpacity also supports accessibility props, focus events, and TV navigation props.
How It Works
TouchableOpacity uses an Animated.View to smoothly animate the opacity:
- On press: Animates to
activeOpacity (default 0.2) with a 150ms duration
- On release: Animates back to original opacity with a 250ms duration
- Uses native driver for better performance
Unlike TouchableHighlight, TouchableOpacity doesn’t require your child to have an explicit background color.
Common Patterns
function Button({ title, onPress, disabled }) {
return (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
style={[
styles.button,
disabled && styles.buttonDisabled,
]}
>
<Text style={styles.buttonText}>{title}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#007AFF',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
},
buttonDisabled: {
backgroundColor: '#ccc',
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
textAlign: 'center',
},
});
import Icon from 'react-native-vector-icons/Ionicons';
function IconButton({ iconName, onPress, size = 24 }) {
return (
<TouchableOpacity
onPress={onPress}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<Icon name={iconName} size={size} color="#333" />
</TouchableOpacity>
);
}
Card Component
function Card({ title, description, onPress }) {
return (
<TouchableOpacity
onPress={onPress}
activeOpacity={0.8}
style={styles.card}
>
<Text style={styles.cardTitle}>{title}</Text>
<Text style={styles.cardDescription}>{description}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: 'white',
padding: 16,
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
},
cardDescription: {
fontSize: 14,
color: '#666',
},
});
List Item
function ListItem({ item, onPress }) {
return (
<TouchableOpacity
onPress={() => onPress(item.id)}
style={styles.listItem}
>
<View style={styles.listItemContent}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subtitle}>{item.subtitle}</Text>
</View>
<Icon name="chevron-right" size={20} color="#ccc" />
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
listItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
listItemContent: {
flex: 1,
},
title: {
fontSize: 16,
fontWeight: '500',
marginBottom: 4,
},
subtitle: {
fontSize: 14,
color: '#666',
},
});
With Loading State
function AsyncButton({ onPress, title, loading }) {
return (
<TouchableOpacity
onPress={onPress}
disabled={loading}
style={[styles.button, loading && styles.buttonLoading]}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.buttonText}>{title}</Text>
)}
</TouchableOpacity>
);
}
Custom Press Feedback
function CustomButton() {
const [pressing, setPressing] = React.useState(false);
return (
<TouchableOpacity
onPressIn={() => setPressing(true)}
onPressOut={() => setPressing(false)}
activeOpacity={0.6}
>
<View style={[styles.button, pressing && styles.buttonPressed]}>
<Text>{pressing ? 'Pressing...' : 'Press Me'}</Text>
</View>
</TouchableOpacity>
);
}
Android Ripple Effect
For Android’s native ripple effect, use TouchableNativeFeedback or Pressable with android_ripple:
import { Pressable, Platform } from 'react-native';
<Pressable
android_ripple={{ color: 'rgba(0, 0, 0, 0.1)' }}
style={({ pressed }) => [
styles.button,
Platform.OS === 'ios' && pressed && { opacity: 0.7 },
]}
>
<Text>Platform-specific feedback</Text>
</Pressable>
Comparison with Other Touchables
- TouchableOpacity: Fades opacity (most commonly used)
- TouchableHighlight: Shows underlay color
- TouchableWithoutFeedback: No visual feedback
- Pressable: Modern alternative with more features
For new projects, consider using Pressable which provides more flexibility and follows modern React patterns.
Accessibility
TouchableOpacity automatically sets appropriate accessibility properties:
<TouchableOpacity
accessible={true}
accessibilityLabel="Submit form"
accessibilityHint="Double tap to submit the form"
accessibilityRole="button"
onPress={handleSubmit}
>
<Text>Submit</Text>
</TouchableOpacity>