Visual Hierarchy
- Use
primary
variant for main actions andoutline
for secondary actions - Limit primary buttons to one per screen section
- Group related actions using consistent spacing and alignment
Button provides a comprehensive solution for interactive elements with built-in animations, loading states, and multiple styling variants. It supports both Pressable and TouchableOpacity components with customizable spring animations and visual feedback.
import { Button, ButtonText, ButtonIcon } from 'rnc-theme';
<Button onPress={handlePress}> <ButtonText>Click Me</ButtonText></Button>
<Button variant="secondary" onPress={handleSubmit}> <ButtonIcon icon={<CheckIcon />} position="left" /> <ButtonText>Submit Form</ButtonText></Button>
<Button loading={isSubmitting} onPress={handleSubmit}> <ButtonText>Save Changes</ButtonText></Button>
<Button disabled={!isValid} variant="primary"> <ButtonText>Continue</ButtonText></Button>
Prop | Type | Default | Description |
---|---|---|---|
variant | ComponentVariant | 'primary' | Visual style variant |
size | ComponentSize | 'md' | Button size (xs, sm, md, lg, xl) |
disabled | boolean | false | Disable button interactions |
loading | boolean | false | Show loading state with spinner |
borderRadius | keyof Theme['components']['borderRadius'] | 'md' | Border radius value |
fullWidth | boolean | false | Make button full container width |
component | 'pressable' | 'touchable' | 'pressable' | Underlying component type |
animationEnabled | boolean | true | Enable press animations |
pressAnimationScale | number | 0.95 | Scale factor during press |
springConfig | SpringConfig | DEFAULT_SPRING_CONFIG | Animation spring configuration |
Prop | Type | Default | Description |
---|---|---|---|
children | React.ReactNode | - | Text content to display |
showLoadingIndicator | boolean | true | Show spinner when loading |
style | StyleProp<TextStyle> | - | Additional text styles |
Prop | Type | Default | Description |
---|---|---|---|
icon | React.ReactNode | - | Icon component to render |
position | 'left' | 'right' | 'center' | 'left' | Icon position relative to text |
marginLeft | keyof Theme['spacing'] | - | Custom left margin |
marginRight | keyof Theme['spacing'] | - | Custom right margin |
Variant | Description | Use Case |
---|---|---|
primary | Primary action button | Main CTAs, submit buttons |
secondary | Secondary action button | Secondary actions |
outline | Outlined transparent button | Alternative actions |
filled | Filled surface button | Form inputs, neutral actions |
ghost | Transparent button | Subtle actions, navigation |
success | Success state button | Confirmation actions |
error | Error/danger button | Destructive actions |
warning | Warning state button | Caution actions |
info | Information button | Info actions |
destructive | Destructive action button | Delete, remove actions |
<VStack spacing="md" padding="lg"> <Button variant="primary" onPress={handleSave}> <ButtonText>Save Changes</ButtonText> </Button>
<Button variant="outline" onPress={handleCancel}> <ButtonText>Cancel</ButtonText> </Button>
<Button variant="destructive" onPress={handleDelete}> <ButtonIcon icon={<TrashIcon />} position="left" /> <ButtonText>Delete Item</ButtonText> </Button></VStack>
<VStack spacing="md" align="center"> <Button size="xs" variant="primary"> <ButtonText>Extra Small</ButtonText> </Button>
<Button size="sm" variant="primary"> <ButtonText>Small</ButtonText> </Button>
<Button size="md" variant="primary"> <ButtonText>Medium</ButtonText> </Button>
<Button size="lg" variant="primary"> <ButtonText>Large</ButtonText> </Button>
<Button size="xl" variant="primary"> <ButtonText>Extra Large</ButtonText> </Button></VStack>
const [loading, setLoading] = useState(false);
const handleAsyncAction = async () => { setLoading(true); try { await performAsyncOperation(); } finally { setLoading(false); }};
<VStack spacing="md"> <Button loading={loading} onPress={handleAsyncAction}> <ButtonText>Process Data</ButtonText> </Button>
<Button loading={loading} variant="secondary" onPress={handleAsyncAction} > <ButtonIcon icon={<UploadIcon />} position="left" /> <ButtonText>Upload File</ButtonText> </Button></VStack>
<VStack spacing="md"> {/* Left Icon */} <Button variant="primary"> <ButtonIcon icon={<PlusIcon />} position="left" /> <ButtonText>Add Item</ButtonText> </Button>
{/* Right Icon */} <Button variant="outline"> <ButtonText>Next Step</ButtonText> <ButtonIcon icon={<ArrowRightIcon />} position="right" /> </Button>
{/* Icon Only */} <Button variant="ghost" size="sm"> <ButtonIcon icon={<HeartIcon />} position="center" /> </Button>
{/* Custom Spacing */} <Button variant="secondary"> <ButtonIcon icon={<DownloadIcon />} position="left" marginRight="md" /> <ButtonText>Download Report</ButtonText> </Button></VStack>
const LoginForm = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false);
const isValid = email.length > 0 && password.length > 6;
return ( <VStack spacing="lg" padding="xl"> <TextInput placeholder="Email" value={email} onChangeText={setEmail} keyboardType="email-address" />
<TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry />
<VStack spacing="md"> <Button fullWidth disabled={!isValid} loading={loading} onPress={handleLogin} > <ButtonText>Sign In</ButtonText> </Button>
<Button fullWidth variant="outline" onPress={handleForgotPassword} > <ButtonText>Forgot Password?</ButtonText> </Button> </VStack>
<HStack spacing="md" justify="center"> <Button variant="ghost" size="sm"> <ButtonIcon icon={<GoogleIcon />} position="left" /> <ButtonText>Google</ButtonText> </Button>
<Button variant="ghost" size="sm"> <ButtonIcon icon={<FacebookIcon />} position="left" /> <ButtonText>Facebook</ButtonText> </Button> </HStack> </VStack> );};
const ProductActions = ({ product, onAddToCart, onBuyNow }) => { const [addingToCart, setAddingToCart] = useState(false);
return ( <VStack spacing="md" padding="lg"> {/* Quantity Selector */} <HStack spacing="md" align="center" justify="center"> <Button size="sm" variant="outline"> <ButtonText>-</ButtonText> </Button> <Text style={{ minWidth: 40, textAlign: 'center' }}>1</Text> <Button size="sm" variant="outline"> <ButtonText>+</ButtonText> </Button> </HStack>
{/* Main Actions */} <VStack spacing="sm"> <Button fullWidth size="lg" variant="primary" onPress={onBuyNow} > <ButtonText>Buy Now - ${product.price}</ButtonText> </Button>
<Button fullWidth size="lg" variant="outline" loading={addingToCart} onPress={async () => { setAddingToCart(true); await onAddToCart(product); setAddingToCart(false); }} > <ButtonIcon icon={<CartIcon />} position="left" /> <ButtonText>Add to Cart</ButtonText> </Button> </VStack>
{/* Secondary Actions */} <HStack spacing="md" justify="space-around"> <Button variant="ghost" size="sm"> <ButtonIcon icon={<HeartIcon />} position="center" /> </Button>
<Button variant="ghost" size="sm"> <ButtonIcon icon={<ShareIcon />} position="center" /> </Button>
<Button variant="ghost" size="sm"> <ButtonIcon icon={<CompareIcon />} position="center" /> </Button> </HStack> </VStack> );};
const CustomAnimatedButton = () => { const buttonRef = useRef<ButtonRef>(null);
const handleSpecialAction = () => { // Trigger custom animation buttonRef.current?.animate('bounce');
// Perform action after animation setTimeout(() => { handleAction(); }, 600); };
return ( <Button ref={buttonRef} variant="primary" animationEnabled={true} pressAnimationScale={0.9} springConfig={{ damping: 15, stiffness: 150, mass: 1 }} onPress={handleSpecialAction} > <ButtonText>Animated Action</ButtonText> </Button> );};
const DynamicButton = ({ status, onRetry, onCancel }) => { const getButtonConfig = () => { switch (status) { case 'idle': return { variant: 'primary', text: 'Start Process', disabled: false, loading: false }; case 'loading': return { variant: 'primary', text: 'Processing...', disabled: true, loading: true }; case 'success': return { variant: 'success', text: 'Completed!', disabled: false, loading: false }; case 'error': return { variant: 'error', text: 'Retry', disabled: false, loading: false }; default: return { variant: 'primary', text: 'Unknown', disabled: true, loading: false }; } };
const config = getButtonConfig();
return ( <HStack spacing="md"> <Button variant={config.variant} disabled={config.disabled} loading={config.loading} onPress={status === 'error' ? onRetry : handleStart} flex={1} > {status === 'success' && ( <ButtonIcon icon={<CheckIcon />} position="left" /> )} {status === 'error' && ( <ButtonIcon icon={<RefreshIcon />} position="left" /> )} <ButtonText>{config.text}</ButtonText> </Button>
{(status === 'loading' || status === 'error') && ( <Button variant="outline" onPress={onCancel}> <ButtonText>Cancel</ButtonText> </Button> )} </HStack> );};
const buttonRef = useRef<ButtonRef>(null);
<Button ref={buttonRef} onPress={() => buttonRef.current?.animate('bounce')}> <ButtonText>Bounce Animation</ButtonText></Button>
const buttonRef = useRef<ButtonRef>(null);
<Button ref={buttonRef} onPress={() => buttonRef.current?.animate('pulse')}> <ButtonText>Pulse Animation</ButtonText></Button>
const buttonRef = useRef<ButtonRef>(null);
<Button ref={buttonRef} onPress={() => buttonRef.current?.animate('shake')}> <ButtonText>Shake Animation</ButtonText></Button>
Visual Hierarchy
primary
variant for main actions and outline
for secondary actionsLoading States
Performance
React.memo
for frequently re-rendered button listsonPress
handlers