EthicsComputerCourses
Join training +91 90532 10052

Pressable & ScrollView

React Native · Lesson 5 of 30 Beginner 6 min read Updated

By the end of this lesson you will be able to make any part of the screen tappable, give clear feedback when the user presses it, and let long content scroll. These two skills turn a static screen into something that feels like a real app.

Pressable: anything can be a button

On the web you add onClick to a <button>. In React Native, you wrap content in Pressable and use onPress. A press is the mobile word for a tap: the finger goes down and lifts up on the same element.

Step 1: make a simple "Order chai" button.

React JSX
import { Pressable, Text, StyleSheet } from "react-native";

export default function OrderButton() {
  return (
    <Pressable style={styles.button} onPress={() => console.log("Chai ordered")}>
      <Text style={styles.label}>Order chai</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: { backgroundColor: "#087ea4", padding: 14, borderRadius: 10, alignItems: "center" },
  label: { color: "white", fontSize: 16, fontWeight: "600" },
});

What you should see: a blue rounded button. Each tap prints "Chai ordered" in the terminal where Metro runs (and in the debugger console).

Showing pressed feedback

A button that does not react to touch feels broken. Pressable can take a function as its style. React Native calls it with { pressed }, which is true while the finger is down.

Step 2: darken the button while pressed and add a long press.

React JSX
import { Pressable, Text } from "react-native";

export default function OrderButton({ onOrder, onShowMenu }) {
  return (
    <Pressable
      onPress={onOrder}
      onLongPress={onShowMenu}
      hitSlop={8}
      android_ripple={{ color: "rgba(255,255,255,0.3)" }}
      style={({ pressed }) => ({
        backgroundColor: pressed ? "#055f7d" : "#087ea4",
        padding: 14,
        borderRadius: 10,
        transform: [{ scale: pressed ? 0.97 : 1 }],
      })}
    >
      <Text style={{ color: "white", textAlign: "center" }}>Order chai</Text>
    </Pressable>
  );
}

What you should see: the button turns darker and shrinks slightly while your finger is on it. On Android, a ripple also spreads from your finger. Holding the button for about half a second calls onLongPress instead of onPress.

  • hitSlop={8} makes the touch area 8 points bigger on every side, without changing how the button looks. Small icons need this, because fingers are much bigger than mouse pointers.
  • android_ripple adds the standard Android ripple effect.
  • disabled={true} turns the button off.

Practise the logic: selecting seats

Tapping to select and unselect items is a very common pattern. The state logic is pure React, so try it here. In your app, each seat would be a Pressable and onClick would be onPress.

React JSXTry it yourself
import { useState } from "react";
import { createRoot } from "react-dom/client";

const seats = ["A1", "A2", "A3", "B1", "B2", "B3"];
const PRICE = 250;

function SeatPicker() {
  const [selected, setSelected] = useState([]);

  function toggle(seat) {
    setSelected((current) =>
      current.includes(seat) ? current.filter((s) => s !== seat) : [...current, seat]
    );
  }

  return (
    <div style={{ fontFamily: "sans-serif" }}>
      <p>Movie at Pune City Mall, ₹{PRICE} per seat</p>
      {seats.map((seat) => {
        const isOn = selected.includes(seat);
        return (
          <button
            key={seat}
            onClick={() => toggle(seat)}
            style={{ margin: 4, padding: 10, background: isOn ? "#087ea4" : "#eee", color: isOn ? "white" : "black" }}
          >
            {seat}
          </button>
        );
      })}
      <p>Selected: {selected.join(", ") || "none"} | Total: ₹{selected.length * PRICE}</p>
    </div>
  );
}

createRoot(document.getElementById("root")).render(<SeatPicker />);

Tap A1 and B2: the summary shows "Selected: A1, B2 | Total: ₹500". Tap A1 again and it is removed.

ScrollView: content taller than the screen

Phone screens are short. When your content is longer, wrap it in ScrollView so the user can swipe through it.

Step 3: show a long list of shop rules.

React JSX
import { ScrollView, Text, StyleSheet } from "react-native";

const rules = Array.from({ length: 30 }, (_, i) => `Rule ${i + 1}: Keep the canteen clean.`);

export default function Rules() {
  return (
    <ScrollView
      style={styles.scroll}
      contentContainerStyle={styles.content}
      showsVerticalScrollIndicator={false}
    >
      {rules.map((rule) => (
        <Text key={rule} style={styles.rule}>{rule}</Text>
      ))}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  scroll: { flex: 1 },
  content: { padding: 16, gap: 12 },
  rule: { fontSize: 16 },
});

What you should see: 30 lines that you can swipe up and down. Notice the two style props:

  • style styles the scroll "window" itself. Give it flex: 1 so it has a fixed height to scroll inside.
  • contentContainerStyle styles the long inner content. Padding and gap go here.

Add horizontal to scroll sideways instead, like a row of cricket player cards.

Pull to refresh

Users expect to pull down on a list to reload it. Pass a RefreshControl to the refreshControl prop:

React JSX
import { useState } from "react";
import { ScrollView, RefreshControl, Text } from "react-native";

export default function Scores() {
  const [refreshing, setRefreshing] = useState(false);
  const [updatedAt, setUpdatedAt] = useState(new Date());

  async function onRefresh() {
    setRefreshing(true);
    await new Promise((resolve) => setTimeout(resolve, 1000)); // pretend to load
    setUpdatedAt(new Date());
    setRefreshing(false);
  }

  return (
    <ScrollView refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}>
      <Text style={{ padding: 16 }}>Scores updated at {updatedAt.toLocaleTimeString()}</Text>
    </ScrollView>
  );
}

What you should see: pulling down shows a spinner for one second, then the time updates.

Common mistakes

  • Putting onPress on a View. View does not have onPress. Wrap it in Pressable.
  • No pressed feedback. Users tap twice when nothing changes. Use the pressed style function.
  • Tiny touch targets. Use padding or hitSlop so targets are at least about 44 to 48 points.
  • Padding on style instead of contentContainerStyle. Inner spacing belongs on contentContainerStyle.
  • ScrollView for very long lists. Use FlatList for long or growing data.

Key points

  • Pressable makes any content tappable with onPress and onLongPress.
  • Pass a function to style to change the look while pressed is true.
  • hitSlop enlarges the touch area; android_ripple adds the Android ripple.
  • ScrollView scrolls content taller than the screen; style the inside with contentContainerStyle.
  • RefreshControl adds pull to refresh.
  • ScrollView renders all children at once, so use FlatList for long lists.

Exercise

Build a "Cricket squad" screen. Show 15 player names in a vertical ScrollView. Each name is a Pressable that turns green when selected and grey when not. A long press on a player prints "Captain: NAME" to the console. Show "Selected: N of 11" at the top, and stop the user from selecting more than 11 players.

Show solution
React JSX
import { useState } from "react";
import { ScrollView, Pressable, Text } from "react-native";

const players = ["Aarav", "Kabir", "Rohan", "Farhan", "Meera", "Isha", "Ananya", "Priya",
  "Dev", "Arjun", "Sara", "Neel", "Zoya", "Om", "Tara"];

export default function Squad() {
  const [picked, setPicked] = useState([]);

  function toggle(name) {
    setPicked((p) => {
      if (p.includes(name)) return p.filter((n) => n !== name);
      if (p.length >= 11) return p;
      return [...p, name];
    });
  }

  return (
    <ScrollView contentContainerStyle={{ padding: 16, gap: 8 }}>
      <Text style={{ fontWeight: "700" }}>Selected: {picked.length} of 11</Text>
      {players.map((name) => (
        <Pressable
          key={name}
          onPress={() => toggle(name)}
          onLongPress={() => console.log("Captain: " + name)}
          style={{ padding: 12, borderRadius: 8, backgroundColor: picked.includes(name) ? "#2e9e5b" : "#ccc" }}
        >
          <Text>{name}</Text>
        </Pressable>
      ))}
    </ScrollView>
  );
}

The updater function returns the old array unchanged when 11 players are already picked, so a 12th tap does nothing.

Quick Quiz

1. Which prop runs code when the user taps a Pressable?

2. How can a Pressable look different while the finger is down?

3. Where should you put padding for the content inside a ScrollView?

4. Why is ScrollView a poor choice for 2,000 contacts?

Learn React Native with a mentor

Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call