# Like Button

> Like button with heart pop, particle burst and live count; exposes aria-pressed.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/like-button
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/like-button.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

```bash
npm install lucide-react
```

## Usage

```tsx title="components/like-button-demo.tsx"
import { LikeButton } from "@/components/like-button"

export default function LikeButtonDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-4 rounded-2xl bg-neutral-100 p-12 dark:bg-neutral-900">
      <LikeButton />
      <LikeButton count={2481} defaultLiked />
    </div>
  )
}
```

## Source

```tsx title="components/like-button.tsx"
"use client"

import * as React from "react"
import { Heart } from "lucide-react"

import { cn } from "@/lib/utils"

const keyframes = `
@keyframes like-button-pop{0%{transform:scale(1)}40%{transform:scale(1.35)}100%{transform:scale(1)}}
@keyframes like-button-burst{from{transform:rotate(var(--a)) translateY(-6px) scale(1);opacity:1}to{transform:rotate(var(--a)) translateY(-22px) scale(0);opacity:0}}
`

export interface LikeButtonProps extends Omit<React.ComponentProps<"button">, "onChange" | "defaultValue"> {
  count?: number
  defaultLiked?: boolean
  onLikedChange?: (liked: boolean) => void
}

export function LikeButton({
  count = 128,
  defaultLiked = false,
  onLikedChange,
  onClick,
  className,
  ...props
}: LikeButtonProps) {
  const [liked, setLiked] = React.useState(defaultLiked)
  const [burst, setBurst] = React.useState(0)
  const total = count + (liked && !defaultLiked ? 1 : 0) - (!liked && defaultLiked ? 1 : 0)

  return (
    <button
      type="button"
      aria-pressed={liked}
      aria-label={`${liked ? "Unlike" : "Like"}, ${total} likes`}
      onClick={(event) => {
        onClick?.(event)
        const next = !liked
        setLiked(next)
        if (next) setBurst((value) => value + 1)
        onLikedChange?.(next)
      }}
      className={cn(
        "group inline-flex h-10 items-center gap-2 rounded-full border px-4 text-sm font-medium tabular-nums select-none",
        "transition-[background-color,border-color,color,transform] duration-200 active:scale-95",
        liked
          ? "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-400"
          : "border-border bg-background text-muted-foreground hover:text-foreground",
        "focus-visible:ring-2 focus-visible:ring-rose-400 focus-visible:ring-offset-2 focus-visible:outline-none",
        className
      )}
      {...props}
    >
      <style href="like-button" precedence="default">
        {keyframes}
      </style>
      <span className="relative inline-flex size-5 items-center justify-center">
        <Heart
          key={liked ? "on" : "off"}
          className={cn("size-5", liked && "fill-current")}
          style={liked ? { animation: "like-button-pop 400ms ease-out" } : undefined}
        />
        {liked &&
          Array.from({ length: 6 }, (_, index) => (
            <span
              key={`${burst}-${index}`}
              aria-hidden
              className="pointer-events-none absolute top-1/2 left-1/2 -mt-[2px] -ml-[2px] size-1 rounded-full bg-rose-500"
              style={
                {
                  "--a": `${index * 60}deg`,
                  animation: "like-button-burst 500ms ease-out forwards",
                } as React.CSSProperties
              }
            />
          ))}
      </span>
      <span>{total}</span>
    </button>
  )
}
```
