# Icon button

> A layered icon-only button whose surface and icon sink on touch down and release on different springs; it runs async actions itself, carries a badge and shows a hint on long press.

- Type: Component (actions)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/icon-button
- Markdown: https://goodui.dev/native/components/icon-button.md
- Registry item: https://goodui.dev/native/r/icon-button.json
- Dependencies: react-native-reanimated
- Keywords: react native icon button, expo icon button, toolbar button, round icon button, icon button with badge, toggle icon button, tooltip on long press

## When to use

- Toolbars, headers and cards where the icon is universally understood: share, more, close, bookmark, mute.
- Toggles that keep their icon, such as bookmark or mute (pass `selected`).
- Icons that carry an unread count (pass `badge`).
- Quick actions backed by a request: return the promise from onPress.

## When not to use

- An action whose icon is ambiguous: use button with a text label.
- The screen's floating main action: use fab.
- Liking or favoriting with a count: use like-button.
- Several mutually exclusive icons: use segmented-control.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/icon-button.json
npx expo install --fix
```

The component lands in `components/goodui-native/icon-button/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs badge, locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { IconButton } from "@/components/goodui-native/icon-button/icon-button";
import { ShareIcon, BellIcon } from "./icons";

export function ArticleHeader({ share, unread }: { share: () => Promise<void>; unread: number }) {
  return (
    <>
      <IconButton
        accessibilityLabel="Share"
        icon={(color, size) => <ShareIcon color={color} size={size} />}
        onPress={share}
      />
      <IconButton
        accessibilityLabel="Notifications"
        variant="ghost"
        badge={unread}
        icon={(color, size) => <BellIcon color={color} size={size} />}
      />
    </>
  );
}
```

## Examples

### Variants, tones and shapes

```tsx
<IconButton accessibilityLabel="Add" variant="solid" tone="accent" icon={plus} />
<IconButton accessibilityLabel="Edit" variant="soft" icon={pencil} />
<IconButton accessibilityLabel="Filter" variant="outline" shape="square" icon={sliders} />
<IconButton accessibilityLabel="Delete" variant="soft" tone="danger" icon={trash} />
```

### A toggle

```tsx
<IconButton
  accessibilityLabel="Save to collection"
  selected={saved}
  onPress={() => setSaved((s) => !s)}
  haptic="select"
  icon={(color, size) => <BookmarkIcon color={color} size={size} filled={saved} />}
/>
```

### Long press opens a menu instead of the hint

```tsx
<IconButton accessibilityLabel="More" icon={dots} onPress={openMenu} onLongPress={openContextMenu} />
```

## API reference

### IconButton

`import { IconButton } from "@/components/goodui-native/icon-button/icon-button";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `icon` (required) | `IconButtonIcon` | – | The icon. Pass a function to get the right color for every variant, tone and the selected state. |
| `accessibilityLabel` (required) | `string` | – | Required: an icon has no text. Also the long-press hint unless `hint` says otherwise. |
| `onPress` | `() => unknown` | – | Return a promise and the icon gives way to a spinner, then a drawn check or an error mark with a shake. Anything else is an instant action. |
| `onLongPress` | `() => void` | – | Replaces the long-press hint with your own action, for example a context menu. |
| `hint` | `string \| false` | – | Text of the bubble shown on long press (and after a short hover on the web). `false` turns it off. |
| `variant` | `"solid" \| "soft" \| "outline" \| "ghost"` | `"soft"` | `solid` for the one main action, `soft` filled quietly, `outline` and `ghost` for toolbars. |
| `tone` | `"success" \| "warning" \| "danger" \| "accent" \| "neutral"` | `"neutral"` | Color family. `neutral` follows the foreground; status tones only for status. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | 32, 40 or 52 pt. Small ones keep a 44 pt touch target. |
| `shape` | `"round" \| "square"` | `"round"` | Circle or rounded square. |
| `selected` | `boolean` | – | For toggles such as bookmark or mute: fills a non-solid button with the solid look. |
| `badge` | `number \| boolean` | – | A count, or `true` for a dot, attached to the top corner. |
| `badgeMax` | `number` | `99` | Highest count shown as a number. Default 99. |
| `badgeRing` | `BadgeRing \| false` | `"surface"` | Surface the badge ring cuts out of. Match the surface the button sits on. |
| `disabled` | `boolean` | `false` | Dims the control and ignores presses. |
| `haptic` | `HapticName \| false` | `"tap"` | Haptic on press. `false` turns it off for this button. |
| `accessibilityHint` | `string` | – | Describes what happens on activation, read after the label. |
| `labels` | `Partial<IconButtonLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- accessibilityLabel is required; the role is button and disabled, busy and selected (for non-solid variants) are reported in accessibilityState.
- A badge count is added to the label ('Notifications, 3 new', 'more than 99 new') and the badge itself is hidden from screen readers.
- The label follows async state: 'Share, in progress', then 'done' or 'failed', and the result is announced.
- Sizes below 44 pt get a hitSlop that brings the touch target to 44 pt.
- The long-press hint repeats the label visually and is hidden from screen readers, which already read the label.
- With `selected` set, the web reports it as a toggle (aria-pressed).

## Motion

- Touch down: the surface scales to 0.9 and the icon to 0.88 inside it on the snappy spring; on release the surface returns on snappy and the icon on morph, a beat later, so the press has depth.
- A press overlay fades in on touch down and out on release.
- selected crossfades a solid fill and the icon color (when icon is a function) with a timing.
- Async: the icon fades to a spinner, then a check draws itself or an alert mark appears with a shake.
- Long press (or a 500 ms hover on the web): a hint bubble rises 6 pt and scales in on the morph spring; it leaves 1.2 s after release or when the pointer leaves.
- Reduced motion: no press scale, no shake; the hint and states only fade.

## Haptics

- tap on press (configurable with the haptic prop; use 'select' for toggles)
- tap when onLongPress runs
- success when the promise resolves, error when it rejects

## Performance

- Press, selected and hint animations run on the UI thread; React renders on status, hint visibility and prop changes only.

## Notes for AI

- Always pass accessibilityLabel. Pass icon as a function (color, size) => node so the icon gets the right color in every variant and when selected.
- Default look is variant='soft' with tone='neutral'. Use variant='solid' tone='accent' only for the screen's one main icon action.
- Return the promise from onPress for spinner, success and error; do not add your own loading state.
- badge takes a number (hidden at 0) or true for a dot. Match badgeRing to the surface behind the button.
- The hint uses the accessibilityLabel unless you pass hint; hint={false} turns it off.
- It hugs its size (alignSelf flex-start). Put several in a row with gap 8.

## Related

- [Button](https://goodui.dev/native/components/button.md): A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.
- [Badge](https://goodui.dev/native/components/badge.md): A count, dot or status badge that springs in when it appears, pops when its value changes, caps at 99+ and shrinks away at zero; BadgeAnchor pins it to an icon or avatar.
- [Floating action button](https://goodui.dev/native/components/fab.md): A floating main action that opens a speed dial (the plus turns into a close mark, a scrim dims the screen, mini actions rise one by one with labels) and, extended, folds its label away while you scroll.
- [Like button](https://goodui.dev/native/components/like-button.md): A like toggle with one moment of delight: the heart squashes and springs back filled, a ring expands and particles burst while the count rolls; it updates optimistically and rolls back if the request fails.

## Guidance for AI tools

Icon button: A layered icon-only button whose surface and icon sink on touch down and release on different springs; it runs async actions itself, carries a badge and shows a hint on long press. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
