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' },
});
PropPurpose
onPressCalled when the user taps and releases inside the component
activeOpacityOpacity value (0 to 1) applied while pressed — the default is 0.2, lower means a more visible dimming effect
onLongPressCalled 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 },
});
Note: Forgetting the visual side of 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.

Next up: Real apps rarely show just one item — the next page covers React Native's tools for efficiently rendering long, dynamic lists: Lists: FlatList & SectionList.