Pressable & ScrollView
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.
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.
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_rippleadds 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.
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.
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:
stylestyles the scroll "window" itself. Give itflex: 1so it has a fixed height to scroll inside.contentContainerStylestyles the long inner content. Padding andgapgo 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:
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
onPresson aView.Viewdoes not haveonPress. Wrap it inPressable. - No pressed feedback. Users tap twice when nothing changes. Use the
pressedstyle function. - Tiny touch targets. Use padding or
hitSlopso targets are at least about 44 to 48 points. - Padding on
styleinstead ofcontentContainerStyle. Inner spacing belongs oncontentContainerStyle. - ScrollView for very long lists. Use
FlatListfor long or growing data.
Key points
Pressablemakes any content tappable withonPressandonLongPress.- Pass a function to
styleto change the look whilepressedis true. hitSlopenlarges the touch area;android_rippleadds the Android ripple.ScrollViewscrolls content taller than the screen; style the inside withcontentContainerStyle.RefreshControladds pull to refresh.ScrollViewrenders all children at once, so useFlatListfor 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
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?
React Native uses onPress for taps.
2. How can a Pressable look different while the finger is down?
The style function receives { pressed } so you can return a different style.
3. Where should you put padding for the content inside a ScrollView?
contentContainerStyle styles the inner content that scrolls.
4. Why is ScrollView a poor choice for 2,000 contacts?
FlatList renders only what is near the screen, so it suits long lists.
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
