Like button

Free · MITReact Native · Expo

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.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/like-button.json

Then run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/like-button.

Usage

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)}
    />
  );
}

When to use

Use it for
  • 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.
Reach for something else
  • 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.

Examples

Reaction pill, red heart

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

Controlled, with a custom star

<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

PropTypeDefault
liked
Controlled state. Update it after the promise from `onLikedChange` resolves; the button shows the new state meanwhile.
boolean—
defaultLiked
Uncontrolled starting state.
booleanfalse
onLikedChange
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.
(liked: boolean) => unknown—
count
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.
number—
format
Intl options for the count, for example `{ notation: "compact" }` for "1.2K".
Intl.NumberFormatOptions—
locale
BCP 47 locale for the count. Defaults to the GooduiLocaleProvider's language, else the device locale.
string—
icon
Draw your own icon (star, bookmark, thumbs up). Receives the state, color and size. Defaults to a heart.
(state: LikeButtonIconState) => ReactNode—
tone
Color of the liked state. `accent` follows the theme; `danger` is the classic red heart.
"accent" | "danger""accent"
variant
`plain` is the bare icon for feeds; `soft` is a pill that fills when liked, for reactions.
"plain" | "soft""plain"
size
Icon 18, 22 or 28 pt.
"sm" | "md" | "lg""md"
burst
Play the particle burst and ring on like. Default `true`.
booleantrue
disabled
Dims the control and ignores presses.
booleanfalse
haptic
Cue on toggle (`pop` by default). `false` turns it off.
HapticName | false"pop"
accessibilityLabel
Read by screen readers; the selected state says whether it is on. Same as `labels.like`.
string—
labels
Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide.
Partial<LikeButtonLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • 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.
  • Haptic: pop on toggle (configurable with the haptic prop)
  • Haptic: error when the request rejects
  • 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.

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.

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.