TouchableOpacity와 Pressable로 탭 처리하기
TouchableOpacity와 최신 Pressable API를 사용해 UI 요소에 탭 피드백을 추가하고, 누르는 중과 누르기를 끝낸 상태의 시각적 피드백을 구현합니다.
TouchableOpacity와 Pressable로 탭 처리하기은(는) CoddyKit의 무료 React Native Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 React Native Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. React Native Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Making Elements Tappable
In React Native, most components like View and Text are not tappable by default — they ignore touch events. To make something respond to taps, wrap it in a touchable component. React Native provides several options: TouchableOpacity (fades the element on press), TouchableHighlight (darkens the background on press), TouchableWithoutFeedback (no visual feedback), and the newer Pressable API (most flexible). TouchableOpacity and Pressable are the two you'll use for the vast majority of UI elements.
import { TouchableOpacity, Text, View } from 'react-native';
export default function TapExample() {
return (
<View style={{ padding: 24 }}>
<TouchableOpacity
onPress={() => console.log('Tapped!')}
style={{ backgroundColor: '#4f86f7', padding: 16, borderRadius: 10 }}
>
<Text style={{ color: '#fff', textAlign: 'center', fontWeight: 'bold' }}>
Tap Me
</Text>
</TouchableOpacity>
</View>
);
}TouchableOpacity and activeOpacity
TouchableOpacity is the most commonly used touchable component. When pressed, it reduces the opacity of its children to give visual feedback that the touch was registered. The default activeOpacity is 0.2 (80% fade). Customize it with the activeOpacity prop: 0.7 for a subtle feedback, 0.9 for almost no change, or 0.1 for a strong fade. The fade animation runs on the native thread, making it smooth even when the JS thread is busy processing other events — this is what keeps it feeling responsive.
import { TouchableOpacity, Text, View, StyleSheet } from 'react-native';
export default function Buttons() {
return (
<View style={styles.col}>
<TouchableOpacity
activeOpacity={0.8} // subtle fade
onPress={() => {}}
style={styles.btn}
>
<Text style={styles.label}>Subtle (0.8)</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.2} // strong fade (default)
onPress={() => {}}
style={styles.btn}
>
<Text style={styles.label}>Strong (0.2)</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
col: { padding: 16, gap: 12 },
btn: { backgroundColor: '#4f86f7', padding: 16, borderRadius: 10, alignItems: 'center' },
label: { color: '#fff', fontWeight: '600', fontSize: 16 },
});onPress, onLongPress, and onPressIn/Out
TouchableOpacity fires several touch events. onPress fires after a complete tap (touch down + touch up). onLongPress fires after the user holds down for ~500ms — useful for context menus or delete confirmations. onPressIn fires immediately when the finger touches the screen (before lift), and onPressOut fires when the finger lifts. onPressIn is used to trigger immediate visual feedback or start animations before the tap completes. Use delayLongPress to customize how long the user must hold before onLongPress fires.
import { TouchableOpacity, Text } from 'react-native';
export default function TouchEvents() {
return (
<TouchableOpacity
onPressIn={() => console.log('Touch started')}
onPressOut={() => console.log('Touch ended')}
onPress={() => console.log('Tap completed!')}
onLongPress={() => console.log('Long pressed!')}
delayLongPress={700} // ms before onLongPress fires
style={{ backgroundColor: '#4f86f7', padding: 20, borderRadius: 10, margin: 20 }}
>
<Text style={{ color: '#fff', textAlign: 'center' }}>
Tap or hold me
</Text>
</TouchableOpacity>
);
}Pressable: The Modern API
Pressable is the newer, more flexible touchable API introduced in React Native 0.63. Its key advantage is that the style and children props can accept functions that receive the current press state ({ pressed: boolean }), making dynamic styling on press trivial without managing separate state variables. Pressable also supports more fine-grained event handling including hitSlop, pressRetentionOffset, and unstable_pressDelay. For new code, prefer Pressable over TouchableOpacity.
import { Pressable, Text, StyleSheet } from 'react-native';
export default function PressableButton({ label, onPress }) {
return (
<Pressable
onPress={onPress}
style={({ pressed }) => [
styles.btn,
pressed && styles.pressed, // apply when finger is down
]}
>
{({ pressed }) => (
<Text style={[styles.label, pressed && styles.pressedLabel]}>
{pressed ? 'Holding...' : label}
</Text>
)}
</Pressable>
);
}
const styles = StyleSheet.create({
btn: { backgroundColor: '#4f86f7', padding: 16, borderRadius: 12, alignItems: 'center' },
pressed: { backgroundColor: '#2563d4', transform: [{ scale: 0.97 }] },
label: { color: '#fff', fontWeight: '700', fontSize: 16 },
pressedLabel: { color: '#c3d8ff' },
});hitSlop: Expanding the Tap Area
Mobile usability guidelines recommend a minimum tap target size of 44×44 points (iOS) or 48×48 dp (Android). Small icons and text links often don't meet this threshold visually, but you can expand the tappable area with hitSlop. It adds invisible space around the component that still responds to touches. Pass an object with top, bottom, left, right values (in points). This lets you keep a small visual icon while ensuring it's easy to tap — critical for navigation arrows, close buttons, and action icons in list rows.
import { TouchableOpacity, Text, StyleSheet, View } from 'react-native';
export default function SmallButton() {
return (
<View style={{ flexDirection: 'row', alignItems: 'center', padding: 16 }}>
<Text style={{ flex: 1, fontSize: 16 }}>Notification item</Text>
{/* Small X button — hitSlop expands the tap area */}
<TouchableOpacity
onPress={() => console.log('Dismissed')}
hitSlop={{ top: 16, bottom: 16, left: 16, right: 16 }}
>
<Text style={styles.closeIcon}>✕</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
closeIcon: { fontSize: 16, color: '#999', padding: 4 },
});Disabled State for Buttons
Use the disabled prop on TouchableOpacity or Pressable to prevent interaction. A disabled touchable ignores all touch events. Visually indicate the disabled state by reducing opacity or changing the color — React Native doesn't do this automatically. Use the opacity style or apply a conditional style based on the disabled prop. Disabled buttons are essential for preventing double-submission of forms, indicating that required fields are incomplete, or showing that a feature requires a premium subscription.
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
function ActionButton({ label, onPress, disabled }) {
return (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
style={[styles.btn, disabled && styles.btnDisabled]}
activeOpacity={0.8}
>
<Text style={[styles.label, disabled && styles.labelDisabled]}>
{label}
</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
btn: { backgroundColor: '#4f86f7', padding: 16, borderRadius: 10, alignItems: 'center' },
btnDisabled: { backgroundColor: '#c4d8ff', opacity: 0.7 },
label: { color: '#fff', fontWeight: 'bold', fontSize: 16 },
labelDisabled: { color: 'rgba(255,255,255,0.6)' },
});TouchableHighlight for Background Color Change
TouchableHighlight changes the background color of the element when pressed, instead of fading opacity like TouchableOpacity. Set underlayColor to the color that appears on press. It must have exactly one child — wrap multiple children in a View. TouchableHighlight is useful for list items and menu rows where a color flash is the expected native behavior (iOS table view style). It is less common in custom UI where designers specify opacity-based feedback.
import { TouchableHighlight, Text, View, StyleSheet } from 'react-native';
export default function ListItem({ title, subtitle, onPress }) {
return (
<TouchableHighlight
onPress={onPress}
underlayColor='#f0f0f0' // highlight color when pressed
activeOpacity={1} // no opacity change, only highlight
>
<View style={styles.row}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.subtitle}>{subtitle}</Text>
</View>
</TouchableHighlight>
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 14, backgroundColor: '#fff', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee' },
title: { fontSize: 16, color: '#222' },
subtitle: { fontSize: 14, color: '#999' },
});Preventing Ghost Clicks in ScrollView
Inside a ScrollView or FlatList, tapping a Pressable or TouchableOpacity can trigger the onPress when the user intended to scroll. The keyboardShouldPersistTaps='handled' prop on the scroll container helps, and Pressable's unstable_pressDelay adds a delay before recognizing a press. For lists, setting delayPressIn on TouchableOpacity gives the scroll handler priority. Understanding touch gesture priority helps you build lists that scroll smoothly without accidentally triggering row taps during scroll.
import { FlatList, TouchableOpacity, Text, View, StyleSheet } from 'react-native';
export default function ScrollableList({ items }) {
return (
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => console.log('Item pressed:', item.title)}
delayPressIn={50} // give scroll a 50ms head start
>
<View style={styles.row}>
<Text style={styles.title}>{item.title}</Text>
</View>
</TouchableOpacity>
)}
keyboardShouldPersistTaps='handled'
/>
);
}
const styles = StyleSheet.create({
row: { padding: 16, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#eee' },
title: { fontSize: 16 },
});Scale Animation on Press with Pressable
Combine Pressable with the Animated API to create a scale effect on press — a popular design pattern where the button slightly shrinks when held. This gives physical depth to flat UI elements. Use Animated.spring triggered by onPressIn and onPressOut. While the full Animated API is covered in a later course, this quick pattern demonstrates Pressable's onPressIn/Out events working alongside a visual animation to produce polished, satisfying button feedback.
import { Pressable, Animated, Text, StyleSheet } from 'react-native';
import { useRef } from 'react';
export default function SpringButton({ label, onPress }) {
const scale = useRef(new Animated.Value(1)).current;
function pressIn() {
Animated.spring(scale, { toValue: 0.93, useNativeDriver: true, speed: 50 }).start();
}
function pressOut() {
Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 50 }).start();
}
return (
<Pressable onPress={onPress} onPressIn={pressIn} onPressOut={pressOut}>
<Animated.View style={[styles.btn, { transform: [{ scale }] }]}>
<Text style={styles.label}>{label}</Text>
</Animated.View>
</Pressable>
);
}
const styles = StyleSheet.create({
btn: { backgroundColor: '#4f86f7', padding: 18, borderRadius: 14, alignItems: 'center' },
label: { color: '#fff', fontWeight: '700', fontSize: 17 },
});Best Practices for Touchable Components
Follow these best practices: always provide visual feedback on press (opacity, color change, or scale — never use TouchableWithoutFeedback for interactive elements). Ensure minimum tap target size of at least 44×44 points. Use accessibilityRole='button' on custom touchable elements so screen readers announce them correctly. Avoid deeply nesting touchables — nested touchables cause gesture conflicts; use stopPropagation patterns or restructure the layout. Prefer Pressable for new code — it's more powerful and the React Native team's recommended choice going forward.
import { Pressable, Text, StyleSheet } from 'react-native';
// Best-practice accessible button
export default function AccessibleButton({ label, onPress, disabled }) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
accessibilityRole='button'
accessibilityState={{ disabled }}
accessibilityLabel={label}
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
style={({ pressed }) => [
styles.btn,
pressed && styles.pressed,
disabled && styles.disabled,
]}
>
<Text style={styles.label}>{label}</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
btn: { minWidth: 44, minHeight: 44, backgroundColor: '#4f86f7', paddingHorizontal: 20, borderRadius: 10, alignItems: 'center', justifyContent: 'center' },
pressed: { opacity: 0.75, transform: [{ scale: 0.97 }] },
disabled: { backgroundColor: '#aac4f7' },
label: { color: '#fff', fontWeight: '700' },
});Icon Buttons and FABs
A Floating Action Button (FAB) is an absolutely positioned, circular button that appears above all content to trigger the primary action on a screen. Use position: 'absolute', bottom, and right (or end for RTL support) to anchor it to the bottom-right of the screen. Wrap the circle in a Pressable or TouchableOpacity with a shadow/elevation for depth. Icon buttons (like a back arrow, close X, or filter icon) follow the same 44×44 minimum tap target rule using hitSlop when the visual size is smaller.
import { TouchableOpacity, Text, StyleSheet, View } from 'react-native';
export default function FAB({ onPress, icon = '+' }) {
return (
<TouchableOpacity
style={styles.fab}
onPress={onPress}
activeOpacity={0.85}
accessibilityRole='button'
accessibilityLabel='Add new item'
>
<Text style={styles.icon}>{icon}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
fab: {
position: 'absolute',
bottom: 24,
right: 24,
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#4f86f7',
alignItems: 'center',
justifyContent: 'center',
// Shadow iOS
shadowColor: '#4f86f7',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.4,
shadowRadius: 8,
// Shadow Android
elevation: 8,
},
icon: { color: '#fff', fontSize: 28, fontWeight: '300', lineHeight: 32 },
});Quick Check
Test your understanding of React Native Mobile Development concepts from this lesson.
Lesson Recap
In this lesson you learned: TouchableOpacity provides opacity-based press feedback and is widely used in existing codebases, Pressable is the modern API with function-based dynamic styles and more precise event control, and hitSlop expands the tap target area for small interactive elements. Next up we explore toggles, switches, and checkboxes.
AI 튜터와 함께 JavaScript을(를) 배우세요 — 무료
브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.
- 코스
- 30
- 레슨
- 120
자주 묻는 질문
“TouchableOpacity와 Pressable로 탭 처리하기” 강의는 무료인가요?
네 — “TouchableOpacity와 Pressable로 탭 처리하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 React Native Academy 강의 전체를 잠금 해제할 수 있습니다. React Native Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“TouchableOpacity와 Pressable로 탭 처리하기”에서 뭘 배우나요?
TouchableOpacity와 최신 Pressable API를 사용해 UI 요소에 탭 피드백을 추가하고, 누르는 중과 누르기를 끝낸 상태의 시각적 피드백을 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 React Native Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
React Native Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 React Native Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“TouchableOpacity와 Pressable로 탭 처리하기” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 React Native Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 React Native Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- TextInput 기초와 키보드 유형
- TouchableOpacity와 Pressable로 탭 처리하기
- 전환 버튼, 스위치 및 체크박스
- 간단한 로그인 양식 만들기