Button / Pressable
React Native button-like components: Button, Pressable, TouchableOpacity, and the platform-aware patterns.
React Native — buttons
EXAMPLE
import { Button, Pressable, TouchableOpacity, View, Text, StyleSheet, Platform } from 'react-native';
// ===== 1. Built-in <Button> (limited styling) =====
<Button title="Submit" onPress={onSubmit} color="#2563eb" />
// Pros: native button on each platform
// Cons: no custom styling; rarely the right choice for design-led apps
// ===== 2. Pressable (recommended, RN 0.63+) =====
<Pressable
onPress={onPress}
onLongPress={onLongPress}
style={({ pressed }) => [
styles.btn,
pressed && styles.btnPressed,
]}
android_ripple={{ color: 'rgba(255,255,255,0.2)' }}
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
accessibilityRole="button"
accessibilityLabel="Submit form"
>
<Text style={styles.btnText}>Submit</Text>
</Pressable>
// ===== 3. TouchableOpacity (legacy but still common) =====
<TouchableOpacity onPress={onPress} activeOpacity={0.7} style={styles.btn}>
<Text style={styles.btnText}>Submit</Text>
</TouchableOpacity>
// ===== Styling (StyleSheet) =====
const styles = StyleSheet.create({
btn: {
backgroundColor: '#2563eb',
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 8,
alignItems: 'center',
},
btnPressed: {
backgroundColor: '#1d4ed8',
},
btnText: {
color: 'white',
fontWeight: '600',
fontSize: 16,
},
});
// ===== A reusable button component =====
function PrimaryButton({ title, onPress, disabled }) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.btn,
disabled && { opacity: 0.5 },
pressed && styles.btnPressed,
]}
android_ripple={!disabled ? { color: 'rgba(255,255,255,0.2)' } : undefined}
accessibilityRole="button"
accessibilityState={{ disabled: !!disabled }}
>
<Text style={styles.btnText}>{title}</Text>
</Pressable>
);
}
// ===== Loading state =====
import { ActivityIndicator } from 'react-native';
function SubmitButton({ onPress, loading }) {
return (
<Pressable onPress={onPress} disabled={loading} style={styles.btn}>
{loading ? <ActivityIndicator color="white" /> : <Text style={styles.btnText}>Submit</Text>}
</Pressable>
);
}
// ===== Icon buttons =====
import { Feather } from '@expo/vector-icons';
<Pressable onPress={onClose} hitSlop={12} style={({ pressed }) => [{ opacity: pressed ? 0.6 : 1 }]}>
<Feather name="x" size={20} color="#0f172a" />
</Pressable>
// ===== Platform-aware patterns =====
const buttonStyle = Platform.select({
ios: { borderRadius: 8 },
android: { borderRadius: 4, elevation: 2 },
});
// ===== Accessibility =====
// - accessibilityRole="button"
// - accessibilityLabel for icon-only buttons
// - accessibilityState={{ disabled, busy }}
// - hitSlop on small tap targets to meet 44x44 minimum
// ===== Patterns to internalise =====
// - Pressable over TouchableOpacity in new code
// - hitSlop for small icon buttons (44x44 minimum tap target)
// - Loading + disabled state encoded both visually and via accessibilityState
// - PrimaryButton / SecondaryButton component pair for the whole app
// ===== Pitfalls =====
// - <Button> is too restrictive; use Pressable for custom UI
// - Inline styles inside hot lists -> new object every render
// - Forgetting hitSlop on tiny icons -> bad UX
// - Disabled visuals without accessibilityState={{ disabled: true }} -> screen reader gap
Why it matters
Pressable is the modern button primitive. Wrap it in a PrimaryButton component with consistent styles, loading + disabled states, accessibility roles, and android_ripple. Use the built-in Button only for prototypes; build your own for any real app.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { Pressable, Text } from 'react-native';
<Pressable
onPress={() => alert('hi')}
style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })}
>
<Text>Tap me</Text>
</Pressable>
Try it Yourself »
Discussion
Loading…