Skip to main content

Button

The Button component is a versatile, animated pressable element with multiple variants, sizes, and states.

Import

import { Button } from 'papillon-ui';

Basic Usage

<Button 
  variant="primary"
  title="Click Me"
  onPress={() => console.log('Pressed!')}
/>

Props

variant
'primary' | 'outline' | 'light' | 'ghost' | 'service'
default:"primary"
The visual style variant of the button.
  • primary: Solid background with white text
  • outline: Transparent background with colored border
  • light: Light colored background
  • ghost: Transparent with subtle text
  • service: Card style with border
title
string
The text to display in the button.
icon
React.ReactNode
Icon element to display before the title.
color
Color
default:"primary"
The color scheme for the button. Options:
  • primary: Theme primary color (#369a82)
  • text: Theme text color
  • light: White (#FFFFFF)
  • danger: Red (#DC1400)
  • cherry: Pink (#D60046)
  • black: Black (#000000)
  • card: Theme card color
  • blue: Blue (#0059DD)
  • orange: Orange (#C94F1A)
size
'small' | 'medium' | 'large'
default:"medium"
The size of the button.
  • small: Height 40px, padding 12px
  • medium: Height 50px, padding 18px
  • large: Height 60px, padding 24px
inline
boolean
default:false
If true, button width adjusts to content instead of full width.
loading
boolean
default:false
Shows a loading spinner inside the button.
disabled
boolean
default:false
Disables the button and reduces opacity.
alignment
'start' | 'center' | 'end'
default:"center"
Horizontal alignment of button content.
disableAnimation
boolean
default:false
Disables layout transition animations.
onPress
() => void
Callback fired when the button is pressed.

Variants

Primary

Solid background button with white text. The default and most prominent style.
<Button 
  variant="primary"
  color="primary"
  title="Primary Button"
  onPress={() => {}}
/>

<Button 
  variant="primary"
  color="danger"
  title="Delete"
  onPress={() => {}}
/>

Outline

Transparent background with colored border and text.
<Button 
  variant="outline"
  color="primary"
  title="Outline Button"
  onPress={() => {}}
/>

Light

Light colored background (30% opacity) with colored text.
<Button 
  variant="light"
  color="primary"
  title="Light Button"
  onPress={() => {}}
/>

Ghost

Minimal transparent button with subtle text.
<Button 
  variant="ghost"
  title="Ghost Button"
  onPress={() => {}}
/>

Service

Card-style button with border, used for service integrations.
<Button 
  variant="service"
  title="PRONOTE"
  icon={<ServiceIcon />}
  onPress={() => {}}
/>

With Icons

Add icons using Lucide React Native or other icon libraries:
import { User, Download, Settings } from 'lucide-react-native';

<Button 
  variant="primary"
  title="Profile"
  icon={<User />}
  onPress={() => {}}
/>

<Button 
  variant="outline"
  title="Download"
  icon={<Download />}
  onPress={() => {}}
/>

Loading State

Display a loading spinner while an async operation is in progress:
const [loading, setLoading] = useState(false);

const handleSubmit = async () => {
  setLoading(true);
  await submitForm();
  setLoading(false);
};

<Button 
  variant="primary"
  title="Submit"
  loading={loading}
  onPress={handleSubmit}
/>

Sizes

<Button 
  variant="primary"
  size="small"
  title="Small Button"
/>

<Button 
  variant="primary"
  size="medium"
  title="Medium Button"
/>

<Button 
  variant="primary"
  size="large"
  title="Large Button"
/>

Inline Buttons

By default, buttons take full width. Use inline to size to content:
<View style={{ flexDirection: 'row', gap: 8 }}>
  <Button 
    variant="outline"
    inline
    title="Cancel"
  />
  <Button 
    variant="primary"
    inline
    title="Confirm"
  />
</View>

Custom Content

Pass custom children for advanced layouts:
<Button variant="primary" onPress={() => {}}>
  <View style={{ flexDirection: 'row', gap: 8 }}>
    <Icon><Star /></Icon>
    <Typography color="light">Star Project</Typography>
    <Typography color="light" variant="caption">250</Typography>
  </View>
</Button>

Disabled State

<Button 
  variant="primary"
  title="Disabled Button"
  disabled
  onPress={() => {}}
/>

Animations

The Button component includes built-in press animations:
  • Press In: Scales to 0.97 and reduces opacity to 0.7
  • Press Out: Springs back to normal scale with smooth easing
  • Haptic Feedback: Soft haptic on press (iOS)

Disable Animations

<Button 
  variant="primary"
  title="No Animation"
  disableAnimation
/>

Alignment

Control horizontal alignment of button content:
<Button 
  variant="primary"
  title="Left Aligned"
  alignment="start"
  icon={<User />}
/>

<Button 
  variant="primary"
  title="Right Aligned"
  alignment="end"
  icon={<ChevronRight />}
/>

Theming

The Button component automatically adapts to your React Navigation theme:
import { useTheme } from '@react-navigation/native';

function ThemedButtons() {
  const { colors } = useTheme();
  
  return (
    <>
      <Button 
        variant="primary"
        color="primary" // Uses theme.colors.primary
        title="Primary"
      />
      
      <Button 
        variant="outline"
        color="text" // Uses theme.colors.text
        title="Outline"
      />
    </>
  );
}

Accessibility

The Button component is built on React Native’s Pressable and inherits all accessibility props.
<Button 
  variant="primary"
  title="Submit"
  accessibilityLabel="Submit form"
  accessibilityHint="Double tap to submit the form"
  accessibilityRole="button"
/>

Performance

  • Button is wrapped in React.memo for optimal re-render performance
  • Animations use Reanimated’s worklet-based animations (runs on UI thread)
  • Press handlers are memoized with useCallback
  • All style calculations are memoized with useMemo

Full Example

import { Button, Typography, Stack } from 'papillon-ui';
import { Save, X } from 'lucide-react-native';
import { useState } from 'react';

function FormActions() {
  const [saving, setSaving] = useState(false);
  
  const handleSave = async () => {
    setSaving(true);
    await saveData();
    setSaving(false);
  };
  
  return (
    <Stack direction="horizontal" gap={12} padding={16}>
      <Button 
        variant="outline"
        color="text"
        title="Cancel"
        icon={<X />}
        inline
        onPress={() => navigation.goBack()}
      />
      
      <Button 
        variant="primary"
        color="primary"
        title="Save Changes"
        icon={<Save />}
        loading={saving}
        onPress={handleSave}
        style={{ flex: 1 }}
      />
    </Stack>
  );
}

Build docs developers (and LLMs) love