Button

Interactive component that triggers an action when pressed.

Import

import { Button } from 'heroui-native';

Anatomy

<Button>
  <Button.Label>...</Button.Label>
</Button>
  • Button: Main container that handles press interactions, animations, and variants. Renders string children as label or accepts compound components for custom layouts.
  • Button.Label: Text content of the button. Inherits size and variant styling from parent Button context.

Usage

Basic Usage

The Button component accepts string children that automatically render as label.

<Button>Basic Button</Button>

With Compound Parts

Use Button.Label for explicit control over the label component.

<Button>
  <Button.Label>Click me</Button.Label>
</Button>

With Icons

Combine icons with labels for enhanced visual communication.

<Button>
  <Icon name="add" size={20} />
  <Button.Label>Add Item</Button.Label>
</Button>

<Button>
  <Button.Label>Download</Button.Label>
  <Icon name="download" size={18} />
</Button>

Icon Only

Create square icon-only buttons using the isIconOnly prop.

<Button isIconOnly>
  <Icon name="heart" size={18} />
</Button>

Sizes

Control button dimensions with three size options.

<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

Variants

Choose from seven visual variants for different emphasis levels.

<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>
<Button variant="danger-soft">Danger Soft</Button>

Feedback Variants

The feedbackVariant prop controls which press feedback effects are rendered:

  • 'scale-highlight' (default): Built-in scale + highlight overlay
  • 'scale-ripple': Built-in scale + ripple overlay
  • 'scale': Built-in scale only (no overlay)
  • 'none': No feedback animations at all
{/* Scale + Highlight (default) */}
<Button feedbackVariant="scale-highlight">Highlight Effect</Button>

{/* Scale + Ripple */}
<Button feedbackVariant="scale-ripple">Ripple Effect</Button>

{/* Scale only */}
<Button feedbackVariant="scale">Scale Only</Button>

{/* No feedback */}
<Button feedbackVariant="none">No Feedback</Button>

Custom Animation

The animation prop controls individual sub-animations. Its shape depends on the feedbackVariant.

{/* Customize scale and highlight (default feedbackVariant) */}
<Button
  animation={{
    scale: { value: 0.97 },
    highlight: {
      backgroundColor: { value: '#3b82f6' },
      opacity: { value: [0, 0.2] },
    },
  }}
>
  Custom Highlight
</Button>

{/* Customize scale and ripple */}
<Button
  feedbackVariant="scale-ripple"
  animation={{
    scale: { value: 0.97 },
    ripple: {
      backgroundColor: { value: '#3b82f6' },
      opacity: { value: [0, 0.3, 0] },
    },
  }}
>
  Custom Ripple
</Button>

Disable Individual Animations

Disable specific sub-animations by setting them to false:

{/* Disable scale, keep highlight */}
<Button animation={{ scale: false }}>No Scale</Button>

{/* Disable highlight, keep scale */}
<Button animation={{ highlight: false }}>No Highlight</Button>

{/* Disable both */}
<Button animation={{ scale: false, highlight: false }}>No Animations</Button>

Disable All Animations

Use animation={false} to disable all feedback, or animation="disable-all" for cascading disable:

<Button animation={false}>Disabled</Button>
<Button animation="disable-all">Disable All (cascading)</Button>

Loading State with Spinner

Transform button to loading state with spinner animation.

const themeColorAccentForeground = useThemeColor('accent-foreground');

<Button
  layout={LinearTransition.springify()}
  variant="primary"
  onPress={() => {
    setIsDownloading(true);
    setTimeout(() => {
      setIsDownloading(false);
    }, 3000);
  }}
  isIconOnly={isDownloading}
  className="self-center"
>
  {isDownloading ? (
    <Spinner entering={FadeIn.delay(50)} color={themeColorAccentForeground} />
  ) : (
    'Download now'
  )}
</Button>;

Custom Background with LinearGradient

Add gradient backgrounds using absolute positioned elements. Use feedbackVariant="none" to disable the default highlight overlay, or use feedbackVariant="scale-ripple" for a custom ripple effect.

import { Button, PressableFeedback } from 'heroui-native';
import { LinearGradient } from 'expo-linear-gradient';
import { StyleSheet } from 'react-native';

{/* Gradient with no feedback overlay */}
<Button feedbackVariant="none">
  <LinearGradient
    colors={['#9333ea', '#ec4899']}
    start={{ x: 0, y: 0 }}
    end={{ x: 1, y: 0 }}
    style={StyleSheet.absoluteFill}
  />
  <Button.Label className="text-white font-bold">Gradient</Button.Label>
</Button>

{/* Gradient with custom ripple effect */}
<Button
  feedbackVariant="scale-ripple"
  animation={{
    ripple: {
      backgroundColor: { value: 'white' },
      opacity: { value: [0, 0.5, 0] },
    },
  }}
>
  <LinearGradient
    colors={['#0d9488', '#ec4899']}
    start={{ x: 0, y: 0 }}
    end={{ x: 1, y: 0 }}
    style={StyleSheet.absoluteFill}
  />
  <Button.Label className="text-white font-bold" pointerEvents="none">
    Gradient with Ripple
  </Button.Label>
</Button>

Example

import { Button, useThemeColor } from 'heroui-native';
import { Ionicons } from '@expo/vector-icons';
import { View } from 'react-native';

export default function ButtonExample() {
  const [
    themeColorAccentForeground,
    themeColorAccentSoftForeground,
    themeColorDangerForeground,
    themeColorDefaultForeground,
  ] = useThemeColor([
    'accent-foreground',
    'accent-soft-foreground',
    'danger-foreground',
    'default-foreground',
  ]);

  return (
    <View className="gap-4 p-4">
      <Button variant="primary">
        <Ionicons name="add" size={20} color={themeColorAccentForeground} />
        <Button.Label>Add Item</Button.Label>
      </Button>

      <View className="flex-row gap-4">
        <Button size="sm" isIconOnly>
          <Ionicons name="heart" size={16} color={themeColorAccentForeground} />
        </Button>
        <Button size="sm" variant="secondary" isIconOnly>
          <Ionicons
            name="bookmark"
            size={16}
            color={themeColorAccentSoftForeground}
          />
        </Button>
        <Button size="sm" variant="danger" isIconOnly>
          <Ionicons name="trash" size={16} color={themeColorDangerForeground} />
        </Button>
      </View>

      <Button variant="tertiary">
        <Button.Label>Learn More</Button.Label>
        <Ionicons
          name="chevron-forward"
          size={18}
          color={themeColorDefaultForeground}
        />
      </Button>
    </View>
  );
}

You can find more examples in the GitHub repository.

API Reference

Button

Button extends all props from PressableFeedback (except animation, which is redefined) with additional button-specific props.

proptypedefaultdescription
variant'primary' | 'secondary' | 'tertiary' | 'outline' | 'ghost' | 'danger' | 'danger-soft''primary'Visual variant of the button
size'sm' | 'md' | 'lg''md'Size of the button
isIconOnlybooleanfalseWhether the button displays an icon only (square aspect ratio)
feedbackVariant'scale-highlight' | 'scale-ripple' | 'scale' | 'none''scale-highlight'Determines which feedback effects are rendered
animationButtonAnimation-Animation configuration (shape depends on feedbackVariant)

For inherited props including isDisabled, className, children, and all Pressable props, see PressableFeedback API Reference.

ButtonAnimation

The animation prop is a discriminated union based on feedbackVariant. It follows the AnimationRoot control flow:

  • true or undefined: Use default animations
  • false or "disabled": Disable all feedback animations
  • "disable-all": Cascade-disable all animations including child compound parts
  • object: Custom configuration with sub-animation keys (see below)

When feedbackVariant="scale-highlight" (default):

proptypedefaultdescription
scalePressableFeedbackScaleAnimation-Scale animation config (false to disable)
highlightPressableFeedbackHighlightAnimation-Highlight overlay config (false to disable)
state'disabled' | 'disable-all' | boolean-Control animation state while keeping config (runtime toggle)

When feedbackVariant="scale-ripple":

proptypedefaultdescription
scalePressableFeedbackScaleAnimation-Scale animation config (false to disable)
ripplePressableFeedbackRippleAnimation-Ripple overlay config (false to disable)
state'disabled' | 'disable-all' | boolean-Control animation state while keeping config (runtime toggle)

When feedbackVariant="scale":

proptypedefaultdescription
scalePressableFeedbackScaleAnimation-Scale animation config (false to disable)
state'disabled' | 'disable-all' | boolean-Control animation state while keeping config (runtime toggle)

When feedbackVariant="none":

Only 'disable-all' is accepted as a string value. All feedback effects are disabled.

For detailed animation sub-types (PressableFeedbackScaleAnimation, PressableFeedbackHighlightAnimation, PressableFeedbackRippleAnimation), see PressableFeedback API Reference.

Button.Label

proptypedefaultdescription
childrenReact.ReactNode-Content to be rendered as label
classNamestring-Additional CSS classes
...TextPropsTextProps-All standard Text props are supported

Hooks

useButton

Hook to access the Button context values. Returns the button's size, variant, and disabled state.

import { useButton } from 'heroui-native';

const { size, variant, isDisabled } = useButton();

Return Value

propertytypedescription
size'sm' | 'md' | 'lg'Size of the button
variant'primary' | 'secondary' | 'tertiary' | 'outline' | 'ghost' | 'danger' | 'danger-soft'Visual variant of the button
isDisabledbooleanWhether the button is disabled

Note: This hook must be used within a Button component. It will throw an error if called outside of the button context.

On this page