---
title: Button
description: Interactive component that triggers an action when pressed.
links:
  source_native: button/button.tsx
  styles_native: button/button.styles.ts
---

<NativeVideoPlayerView
  target="auto"
  srcLight="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/native/components/videos/button-docs-light.mp4"
  srcDark="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/native/components/videos/button-docs-dark.mp4"
/>

## Import

```tsx
import { Button } from 'heroui-native';
```

## Anatomy

```tsx
<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.Background**: Optional theme-aware background container rendered behind the button surface. Mounted automatically for the `secondary` and `tertiary` variants when the active theme registers default background content (e.g. `glass`). Replace or remove it via the `background` prop.
- **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.

```tsx
<Button>Basic Button</Button>
```

### With Compound Parts

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

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

### With Icons

Combine icons with labels for enhanced visual communication.

```tsx
<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.

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

### Sizes

Control button dimensions with three size options.

```tsx
<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.

```tsx
<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

```tsx
{/* 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`.

```tsx
{/* 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`:

```tsx
{/* 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:

```tsx
<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.

```tsx
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.

```tsx
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

```tsx
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](<https://github.com/heroui-inc/heroui-native/blob/main/example/src/app/(home)/components/button.tsx>).

## API Reference

### Button

Button extends all props from [PressableFeedback](./pressable-feedback) (except `animation`, which is redefined) with additional button-specific props.

| prop              | type                                                                                          | default             | description                                                    |
| ----------------- | --------------------------------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------- |
| `variant`         | `'primary' \| 'secondary' \| 'tertiary' \| 'outline' \| 'ghost' \| 'danger' \| 'danger-soft'` | `'primary'`         | Visual variant of the button                                   |
| `size`            | `'sm' \| 'md' \| 'lg'`                                                                        | `'md'`              | Size of the button                                             |
| `isIconOnly`      | `boolean`                                                                                     | `false`             | Whether the button displays an icon only (square aspect ratio) |
| `feedbackVariant` | `'scale-highlight' \| 'scale-ripple' \| 'scale' \| 'none'`                                    | `'scale-highlight'` | Determines which feedback effects are rendered                 |
| `animation`       | `ButtonAnimation`                                                                             | -                   | Animation configuration (shape depends on `feedbackVariant`)   |
| `background` | `React.ReactNode` | - | Background layer behind the button surface. `undefined` renders the theme-aware default for the `secondary` and `tertiary` variants; custom node replaces it; `null` removes it |

For inherited props including `isDisabled`, `className`, `children`, and all Pressable props, see [PressableFeedback API Reference](./pressable-feedback#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):**

| prop        | type                                     | default | description                                                  |
| ----------- | ---------------------------------------- | ------- | ------------------------------------------------------------ |
| `scale`     | `PressableFeedbackScaleAnimation`        | -       | Scale animation config (`false` to disable)                  |
| `highlight` | `PressableFeedbackHighlightAnimation`    | -       | Highlight overlay config (`false` to disable)                |
| `state`     | `'disabled' \| 'disable-all' \| boolean` | -       | Control animation state while keeping config (runtime toggle) |

**When `feedbackVariant="scale-ripple"`:**

| prop     | type                                     | default | description                                                  |
| -------- | ---------------------------------------- | ------- | ------------------------------------------------------------ |
| `scale`  | `PressableFeedbackScaleAnimation`        | -       | Scale animation config (`false` to disable)                  |
| `ripple` | `PressableFeedbackRippleAnimation`       | -       | Ripple overlay config (`false` to disable)                   |
| `state`  | `'disabled' \| 'disable-all' \| boolean` | -       | Control animation state while keeping config (runtime toggle) |

**When `feedbackVariant="scale"`:**

| prop    | type                                     | default | description                                                  |
| ------- | ---------------------------------------- | ------- | ------------------------------------------------------------ |
| `scale` | `PressableFeedbackScaleAnimation`        | -       | 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](./pressable-feedback#api-reference).

### Button.Background

Absolute-fill container rendered behind the button surface. With no children, the active library theme decides the default content (e.g. a glass blur layer); pass children to host custom content with the same positioning and clipping.

| prop           | type              | default | description                                    |
| -------------- | ----------------- | ------- | ---------------------------------------------- |
| `children`     | `React.ReactNode` | -       | Custom content inside the background container |
| `className`    | `string`          | -       | Additional CSS classes                         |
| `...ViewProps` | `ViewProps`       | -       | All standard View props are supported          |

### Button.Label

| prop           | type              | default | description                           |
| -------------- | ----------------- | ------- | ------------------------------------- |
| `children`     | `React.ReactNode` | -       | Content to be rendered as label       |
| `className`    | `string`          | -       | Additional CSS classes                |
| `...TextProps` | `TextProps`       | -       | All standard Text props are supported |

## Hooks

### useButton

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

```tsx
import { useButton } from 'heroui-native';

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

#### Return Value

| property     | type                                                                                          | description                    |
| ------------ | --------------------------------------------------------------------------------------------- | ------------------------------ |
| `size`       | `'sm' \| 'md' \| 'lg'`                                                                        | Size of the button             |
| `variant`    | `'primary' \| 'secondary' \| 'tertiary' \| 'outline' \| 'ghost' \| 'danger' \| 'danger-soft'` | Visual variant of the button   |
| `isDisabled` | `boolean`                                                                                     | Whether 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.
