Skip to content

Button

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>
PropTypeDefaultDescription
variantComponentVariant'primary'Visual style variant
sizeComponentSize'md'Button size (xs, sm, md, lg, xl)
disabledbooleanfalseDisable button interactions
loadingbooleanfalseShow loading state with spinner
borderRadiuskeyof Theme['components']['borderRadius']'md'Border radius value
fullWidthbooleanfalseMake button full container width
component'pressable' | 'touchable''pressable'Underlying component type
animationEnabledbooleantrueEnable press animations
pressAnimationScalenumber0.95Scale factor during press
springConfigSpringConfigDEFAULT_SPRING_CONFIGAnimation spring configuration
PropTypeDefaultDescription
childrenReact.ReactNode-Text content to display
showLoadingIndicatorbooleantrueShow spinner when loading
styleStyleProp<TextStyle>-Additional text styles
PropTypeDefaultDescription
iconReact.ReactNode-Icon component to render
position'left' | 'right' | 'center''left'Icon position relative to text
marginLeftkeyof Theme['spacing']-Custom left margin
marginRightkeyof Theme['spacing']-Custom right margin
VariantDescriptionUse Case
primaryPrimary action buttonMain CTAs, submit buttons
secondarySecondary action buttonSecondary actions
outlineOutlined transparent buttonAlternative actions
filledFilled surface buttonForm inputs, neutral actions
ghostTransparent buttonSubtle actions, navigation
successSuccess state buttonConfirmation actions
errorError/danger buttonDestructive actions
warningWarning state buttonCaution actions
infoInformation buttonInfo actions
destructiveDestructive action buttonDelete, 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>

Visual Hierarchy

  • Use primary variant for main actions and outline for secondary actions
  • Limit primary buttons to one per screen section
  • Group related actions using consistent spacing and alignment

Loading States

  • Always provide loading feedback for async operations
  • Disable buttons during loading to prevent multiple submissions
  • Use descriptive loading text that indicates the current action

Performance

  • Use React.memo for frequently re-rendered button lists
  • Avoid creating new functions in render for onPress handlers
  • Consider debouncing for buttons that trigger expensive operations