Create & Style Custom Buttons in React Native

Build production-ready, accessible, and performant custom button components using Pressable, TouchableOpacity, and modern React Native styling patterns.

Why Custom Buttons Matter in React Native

React Native provides several built-in components for creating touchable elements, but the default Button component has significant limitations that prevent meaningful customization. For production applications that require brand-consistent UI elements, developers must leverage more flexible components like Pressable and TouchableOpacity to create buttons that match their design system while maintaining excellent performance and accessibility.

The evolution from TouchableOpacity to Pressable represents a significant advancement in React Native's approach to touch interactions. Pressable provides a more modern, flexible API that supports multiple interaction states, better accessibility features, and more granular control over touch behavior. Understanding when and how to use these components is essential for building professional-grade mobile applications.

Custom button components also enable consistent implementation of design tokens, interactive states, and brand-specific styling across your entire application. Rather than duplicating button code throughout your project, a well-designed reusable button component ensures visual and functional consistency while simplifying maintenance and updates.

For teams building comprehensive React Native applications as part of their web development services, establishing a component library with reusable buttons is foundational to maintaining design consistency across iOS and Android platforms while accelerating development velocity.

Understanding React Native Button Components in 2025

React Native's button ecosystem has evolved significantly, with Pressable emerging as the recommended approach for custom buttons. While the basic Button component remains useful for simple use cases, its inflexibility makes it unsuitable for most production applications.

The native Button component provides minimal styling options and only supports a single onPress handler. It lacks support for custom icons, flexible layouts, or visual feedback customization. This limitation is intentional, as the component prioritizes consistency across platforms over customization.

Pressable, introduced as a more powerful alternative, wraps any component and provides callbacks for various interaction states including pressed, hovered, focused, and long press. This granular control enables developers to create rich, responsive button experiences that feel native to each platform while maintaining a single codebase.

TouchableOpacity remains available for backward compatibility and simpler use cases, but the React Native team recommends Pressable for new development due to its superior API and cross-platform consistency.

When building comprehensive mobile applications, the choice of button component impacts not just visual presentation but also accessibility compliance and user experience across different devices and interaction methods.

TouchableOpacity vs Pressable Feature Comparison
FeatureTouchableOpacityPressable
Interaction StatesSingle onPressonPress, onLongPress, onPressIn, onPressOut
Visual FeedbackOpacity onlyFull control over all visual states
Hover SupportNoYes (desktop mouse interaction)
Platform ConsistencyVaries by OSUnified API across platforms
AccessibilityBasicEnhanced with accessibilityState
Hit SlopNot supportedConfigurable touch area

Pressable's Advanced Features

Pressable provides several advanced features that make it the superior choice for custom button development. The component exposes callbacks for each phase of a touch interaction, allowing developers to create sophisticated feedback animations and behaviors.

Multiple Interaction States

Pressable exposes four distinct callback props that fire at different points during a touch interaction:

  • onPressIn: Fired immediately when the user touches the element
  • onPressOut: Fired when the touch is released or cancelled
  • onPress: Fired when the touch completes successfully
  • onLongPress: Fired when the touch is held for an extended period

This granular control enables creating realistic button animations that respond naturally to user input, such as scale effects that begin immediately on touch and complete when the touch releases.

Enhanced Touch Area Configuration

The hitSlop property allows expanding the touchable area beyond the component's bounds, which is crucial for creating accessible buttons with adequate touch targets. This is particularly important for users with motor impairments who may have difficulty targeting small interactive elements.

Platform-Specific Optimizations

Pressable automatically handles platform-specific touch behaviors, including Android's ripple effect and iOS's touch feedback. Developers can further customize these behaviors using platform-specific style properties while maintaining a single codebase.

Production-Ready Button Component with Pressable
1import React, { useState, useCallback, useMemo } from 'react';2import { Pressable, StyleSheet, Text, ActivityIndicator, View } from 'react-native';3 4export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';5export type ButtonSize = 'sm' | 'md' | 'lg';6 7interface ButtonProps {8 title: string;9 onPress: () => void;10 variant?: ButtonVariant;11 size?: ButtonSize;12 disabled?: boolean;13 loading?: boolean;14 leftIcon?: React.ReactNode;15 rightIcon?: React.ReactNode;16 accessibilityLabel?: string;17 accessibilityHint?: string;18 testID?: string;19}20 21const VARIANT_STYLES = {22 primary: {23 backgroundColor: '#007AFF',24 borderWidth: 0,25 },26 secondary: {27 backgroundColor: '#585859',28 borderWidth: 0,29 },30 outline: {31 backgroundColor: 'transparent',32 borderWidth: 1,33 borderColor: '#007AFF',34 },35 ghost: {36 backgroundColor: 'transparent',37 borderWidth: 0,38 },39};40 41const SIZE_STYLES = {42 sm: { paddingVertical: 8, paddingHorizontal: 12, fontSize: 14 },43 md: { paddingVertical: 12, paddingHorizontal: 16, fontSize: 16 },44 lg: { paddingVertical: 16, paddingHorizontal: 24, fontSize: 18 },45};46 47export const Button: React.FC<ButtonProps> = ({48 title,49 onPress,50 variant = 'primary',51 size = 'md',52 disabled = false,53 loading = false,54 leftIcon,55 rightIcon,56 accessibilityLabel,57 accessibilityHint,58 testID,59}) => {60 const [pressed, setPressed] = useState(false);61 62 const handlePressIn = useCallback(() => setPressed(true), []);63 const handlePressOut = useCallback(() => setPressed(false), []);64 65 const handlePress = useCallback(() => {66 if (!disabled && !loading) {67 onPress();68 }69 }, [disabled, loading, onPress]);70 71 const buttonStyle = useMemo(() => [72 styles.button,73 VARIANT_STYLES[variant],74 SIZE_STYLES[size],75 pressed && styles.pressed,76 (disabled || loading) && styles.disabled,77 ], [variant, size, pressed, disabled, loading]);78 79 const textStyle = useMemo(() => [80 styles.text,81 SIZE_STYLES[size],82 variant === 'outline' && styles.textOutline,83 variant === 'ghost' && styles.textGhost,84 ], [variant, size]);85 86 return (87 <Pressable88 onPress={handlePress}89 onPressIn={handlePressIn}90 onPressOut={handlePressOut}91 disabled={disabled || loading}92 accessibilityLabel={accessibilityLabel || title}93 accessibilityHint={accessibilityHint}94 accessibilityRole="button"95 accessibilityState={{ disabled: disabled || loading }}96 testID={testID}97 style={({ pressed }) => [98 buttonStyle,99 pressed && styles.pressed,100 ]}101 >102 {loading ? (103 <ActivityIndicator color={variant === 'primary' ? '#FFFFFF' : '#007AFF'} />104 ) : (105 <View style={styles.contentContainer}>106 {leftIcon && <View style={styles.iconLeft}>{leftIcon}</View>}107 <Text style={textStyle}>{title}</Text>108 {rightIcon && <View style={styles.iconRight}>{rightIcon}</View>}109 </View>110 )}111 </Pressable>112 );113};114 115const styles = StyleSheet.create({116 button: {117 borderRadius: 8,118 alignItems: 'center',119 justifyContent: 'center',120 flexDirection: 'row',121 },122 contentContainer: {123 flexDirection: 'row',124 alignItems: 'center',125 justifyContent: 'center',126 },127 text: {128 fontWeight: '600',129 color: '#FFFFFF',130 },131 textOutline: {132 color: '#007AFF',133 },134 textGhost: {135 color: '#007AFF',136 },137 iconLeft: {138 marginRight: 8,139 },140 iconRight: {141 marginLeft: 8,142 },143 pressed: {144 opacity: 0.7,145 transform: [{ scale: 0.98 }],146 },147 disabled: {148 opacity: 0.5,149 },150});
Performance Optimization Techniques

Best practices for creating performant React Native buttons

StyleSheet.create() Benefits

Using StyleSheet.create() creates styles at build time rather than render time, reducing memory allocation and improving performance through style caching and reference equality.

useMemo for Expensive Styles

Memoize style objects that depend on multiple props to prevent unnecessary re-renders when parent components update but button styles haven't changed.

React.memo for Component Memoization

Wrap your button component with React.memo to prevent re-renders when props haven't changed, especially important in list items and form submissions.

Callback Stabilization

Use useCallback for event handlers to maintain referential equality across renders, preventing child component re-renders that depend on button callbacks.

Accessibility Implementation

Creating accessible buttons is not just good practice--it is essential for reaching all users, including those who rely on assistive technologies. React Native provides robust accessibility APIs that custom button components should leverage fully.

Essential Accessibility Properties

The accessibilityLabel prop provides a custom label for screen readers when the button text may be insufficient or unclear. This is particularly important for icon-only buttons or buttons with dynamic text content.

<Pressable
 accessibilityLabel="Submit form button"
 accessibilityHint="Submits the contact form and sends your message"
 accessibilityRole="button"
>
 <Text>Submit</Text>
</Pressable>

The accessibilityState object communicates the button's current state to assistive technologies, enabling users to understand whether a button is disabled, selected, or in another state.

Touch Target Requirements

Both iOS and Android have established minimum touch target sizes to ensure buttons are easily tappable. Apple recommends 44x44 points for iOS, while Google recommends 48x48 dp for Android. Your custom button component should accommodate these requirements through padding and layout.

Platform-Specific Testing

Accessibility testing should occur on actual devices using platform-specific assistive technologies. On iOS, enable VoiceOver in Settings > Accessibility > VoiceOver. On Android, enable TalkBack in Settings > Accessibility > TalkBack. Test button interactions, verify that accessibility labels are announced correctly, and confirm that disabled states are properly communicated.

For teams building accessible React Native applications, consider working with specialists in web development services who understand the broader landscape of accessible component design across platforms.

Animated Button Interactions

Adding purposeful animations to buttons enhances the user experience by providing immediate feedback and creating a polished, professional feel. React Native's Animated API enables creating smooth, performant animations that run on the native thread.

Press Animations

A subtle scale animation on press creates tactile feedback that reinforces the button's interactivity:

import { Animated, Pressable } from 'react-native';

const AnimatedButton = ({ title, onPress }) => {
 const scale = new Animated.Value(1);

 const handlePressIn = () => {
 Animated.spring(scale, {
 toValue: 0.95,
 useNativeDriver: true,
 }).start();
 };

 const handlePressOut = () => {
 Animated.spring(scale, {
 toValue: 1,
 useNativeDriver: true,
 }).start();
 };

 return (
 <Animated.View style={{ transform: [{ scale }] }}>
 <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
 <Text>{title}</Text>
 </Pressable>
 </Animated.View>
 );
};

Using useNativeDriver: true ensures animations run on the native thread, avoiding the JavaScript thread and providing smoother 60fps animations without impacting React component rendering performance.

Loading State Animations

For buttons that perform asynchronous operations, loading animations communicate progress while preventing double submissions. The ActivityIndicator component provides a native loading spinner, while custom animations can provide more branded loading experiences.

Cross-Platform Considerations

React Native's cross-platform philosophy means your button components should work seamlessly on both iOS and Android, but platform-specific differences require thoughtful handling to provide native-feeling experiences.

Platform-Specific Styling

Android devices display a ripple effect by default when touching interactive elements, while iOS provides a subtle highlight. Pressable handles these differences automatically, but you may want to customize the experience using the android_ripple property:

<Pressable
 android_ripple={{ color: '#0055AA', borderless: false }}
 style={({ pressed }) => ({
 opacity: pressed ? 0.8 : 1,
 })}
>
 <Text>Button</Text>
</Pressable>

Platform Module

For styling differences that require more substantial changes, React Native's Platform module enables platform-specific code paths:

import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
 button: {
 ...Platform.select({
 ios: {
 shadowColor: '#000',
 shadowOffset: { width: 0, height: 2 },
 shadowOpacity: 0.1,
 shadowRadius: 4,
 },
 android: {
 elevation: 4,
 },
 }),
 },
});

Material Design and Human Interface Guidelines

Following platform design guidelines helps users feel familiar with your interface. Material Design (Android) emphasizes elevation and ripple effects, while Apple's Human Interface Guidelines (iOS) prefer subtle blurs and more contained visual hierarchies.

For organizations building cross-platform applications that integrate intelligent automation features, partnering with an AI automation team can help create sophisticated user experiences that leverage button interactions for complex workflows.

Frequently Asked Questions

Need Help Building React Native Applications?

Our team specializes in creating performant, accessible mobile applications with React Native. From custom component libraries to full application development, we can help bring your mobile vision to life.

Sources

  1. React Native Official Documentation - Pressable - Official component documentation
  2. React Native Official Documentation - TouchableOpacity - Legacy component reference
  3. React Native Official Documentation - StyleSheet - Performance optimization guide
  4. React Native Official Documentation - Animations - Animation API documentation
  5. React Native Official Documentation - Accessibility - Accessibility best practices