# Like button

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

- Type: Component (actions)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/like-button
- Markdown: https://goodui.dev/native/components/like-button.md
- Registry item: https://goodui.dev/native/r/like-button.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native like button, heart animation, animated like, favorite button, optimistic like, like counter, twitter like animation, reaction button

## When to use

- Liking or favoriting content in a feed, a gallery or a product card.
- Reactions with a visible count (soft variant), such as on comments or posts.
- Saving or starring with a custom icon and the same burst.

## When not to use

- Toggles without delight or count, such as mute or bookmark in a toolbar: use icon-button with selected.
- Settings switches.
- Rating with several levels: use a rating 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/like-button.json
npx expo install --fix
```

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

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 { LikeButton } from "@/components/goodui-native/like-button/like-button";

export function PostActions({ post }: { post: Post }) {
  return (
    <LikeButton
      defaultLiked={post.likedByMe}
      count={post.likes}
      format={{ notation: "compact" }}
      onLikedChange={(liked) => api.setLike(post.id, liked)}
    />
  );
}
```

## Examples

### Reaction pill, red heart

```tsx
<LikeButton variant="soft" tone="danger" size="sm" count={12} defaultLiked={false} />
```

### Controlled, with a custom star

```tsx
<LikeButton
  liked={starred}
  onLikedChange={async (next) => {
    await api.star(repo, next);
    setStarred(next);
  }}
  accessibilityLabel="Star"
  icon={({ liked, color, size }) => <StarIcon filled={liked} color={color} size={size} />}
/>
```

## API reference

### LikeButton

`import { LikeButton } from "@/components/goodui-native/like-button/like-button";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `liked` | `boolean` | – | Controlled state. Update it after the promise from `onLikedChange` resolves; the button shows the new state meanwhile. |
| `defaultLiked` | `boolean` | `false` | Uncontrolled starting state. |
| `onLikedChange` | `(liked: boolean) => unknown` | – | Called with the new state. Return a promise to make the change optimistic: the button flips at once and flips back, with an error haptic and a shake, if the promise rejects. |
| `count` | `number` | – | The total as your server reports it, including the viewer's like at that moment. The button adds or removes the viewer's like on top of it until the next count arrives. |
| `format` | `Intl.NumberFormatOptions` | – | Intl options for the count, for example `{ notation: "compact" }` for "1.2K". |
| `locale` | `string` | – | BCP 47 locale for the count. Defaults to the GooduiLocaleProvider's language, else the device locale. |
| `icon` | `(state: LikeButtonIconState) => ReactNode` | – | Draw your own icon (star, bookmark, thumbs up). Receives the state, color and size. Defaults to a heart. |
| `tone` | `"accent" \| "danger"` | `"accent"` | Color of the liked state. `accent` follows the theme; `danger` is the classic red heart. |
| `variant` | `"plain" \| "soft"` | `"plain"` | `plain` is the bare icon for feeds; `soft` is a pill that fills when liked, for reactions. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Icon 18, 22 or 28 pt. |
| `burst` | `boolean` | `true` | Play the particle burst and ring on like. Default `true`. |
| `disabled` | `boolean` | `false` | Dims the control and ignores presses. |
| `haptic` | `HapticName \| false` | `"pop"` | Cue on toggle (`pop` by default). `false` turns it off. |
| `accessibilityLabel` | `string` | – | Read by screen readers; the selected state says whether it is on. Same as `labels.like`. |
| `labels` | `Partial<LikeButtonLabels>` | – | 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

- Role button with accessibilityLabel (default 'Like'); selected in accessibilityState says whether it is liked; busy while the request is pending.
- The formatted count is the accessibilityValue; the rolling digits are hidden.
- A failed request is announced: 'Couldn't update. Try again.'
- A vertical hitSlop brings the target to 44 pt.
- On the web it is a toggle (aria-pressed) named by visually hidden text that includes the count ('Like, 1,284'), so the name always matches what is on screen.

## Motion

- Like: the icon drops to 0.55 and springs back on the bouncy spring while the filled icon crossfades in; a ring expands from 0.4 to 1.6 and fades, and eight particles in long and short rays fly out, slow down and fade over 600 ms.
- Unlike: the icon dips to 0.82 and returns on the snappy spring, no burst.
- The count rolls through rolling-number: up on like, down on unlike or rollback.
- Press scales the button to 0.94 from touch down; a rejected request shakes it.
- The animation plays whenever the shown state changes, including a `liked` prop set from elsewhere (for example a double tap on a photo).
- Reduced motion: no burst, ring, squash, press scale or shake; the icon crossfades and the count changes in place.

## Haptics

- pop on toggle (configurable with the haptic prop)
- error when the request rejects

## Performance

- The burst is nine views driven by one shared value on the UI thread, mounted only when burst is on and motion is not reduced.
- React renders on toggle, on settle and when count changes.

## Notes for AI

- Return the promise from onLikedChange for optimistic updates with rollback. Do not keep your own pending flag.
- count is the server total including the viewer's like at the time it was fetched. Do not add or subtract the viewer's like yourself.
- Controlled: set liked after the promise resolves; the button shows the new state meanwhile.
- Default tone is accent. tone='danger' gives the classic red heart.
- A custom icon is a function of { liked, color, size }; draw a filled shape when liked is true.

## Related

- [Rolling number](https://goodui.dev/native/components/rolling-number.md): A number whose digits roll in the direction of change, with digits that open and close in place and currency signs that glide instead of jumping.
- [Icon button](https://goodui.dev/native/components/icon-button.md): 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.
- [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.

## Guidance for AI tools

Like button: 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. 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
