# Toggle Like Bookmark — React Toggle Switch

> Icon toggles that each fill in their own way: the heart pops, the bookmark drops in from above, the star spins and the bell rings, all with a ring burst.

- Category: [Toggles & Switches](https://pixelpapa.com/components/toggles.md)
- Tags: toggle, icon, like, bookmark, pop
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/toggles/toggle-like-bookmark

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/toggle-like-bookmark.json"
```

The file is written to `components/pixelpapa/toggle-like-bookmark.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { Bell, Bookmark, Heart, Star, type LucideIcon } from "lucide-react";
import { motion, useReducedMotion, type TargetAndTransition } from "motion/react";
import { useState } from "react";
import { cn } from "@/lib/utils";

type Effect = "pop" | "drop" | "spin" | "ring";

type IconToggleProps = {
  icon: LucideIcon;
  /** Accessible name, e.g. "Like". */
  label: string;
  effect?: Effect;
  pressed?: boolean;
  defaultPressed?: boolean;
  onPressedChange?: (pressed: boolean) => void;
  /** Text colour class used while pressed. */
  activeClassName?: string;
  className?: string;
};

const effects: Record<Effect, TargetAndTransition> = {
  pop: { scale: [1, 0.55, 1.3, 1], transition: { duration: 0.5, times: [0, 0.2, 0.6, 1] } },
  drop: { y: [-16, 3, 0], scaleY: [1.25, 0.85, 1], opacity: [0, 1, 1], transition: { duration: 0.45, times: [0, 0.6, 1] } },
  spin: { rotate: [0, 144], scale: [1, 1.35, 1], transition: { duration: 0.55, ease: [0.3, 1.4, 0.5, 1] } },
  ring: { rotate: [0, -24, 20, -14, 9, -4, 0], transition: { duration: 0.7 } },
};

export function IconToggle({
  icon: Icon,
  label,
  effect = "pop",
  pressed,
  defaultPressed = false,
  onPressedChange,
  activeClassName = "text-rose-500",
  className,
}: IconToggleProps) {
  const [internal, setInternal] = useState(defaultPressed);
  const [burst, setBurst] = useState(0);
  const reduce = useReducedMotion();
  const on = pressed ?? internal;

  function toggle() {
    setInternal(!on);
    if (!on) setBurst((b) => b + 1);
    onPressedChange?.(!on);
  }

  return (
    <button
      type="button"
      aria-pressed={on}
      aria-label={label}
      onClick={toggle}
      className={cn(
        "relative flex size-10 items-center justify-center rounded-full transition-colors duration-200 hover:bg-muted [-webkit-tap-highlight-color:transparent]",
        on ? activeClassName : "text-muted-foreground hover:text-foreground",
        className,
      )}
    >
      {on && burst > 0 && !reduce && (
        <motion.span
          key={burst}
          aria-hidden
          className="pointer-events-none absolute inset-1.5 rounded-full border-2 border-current"
          initial={{ scale: 0.4, opacity: 0.6 }}
          animate={{ scale: 1.5, opacity: 0 }}
          transition={{ duration: 0.5, ease: "easeOut" }}
        />
      )}
      {/* Keyed by burst so each press replays the entrance from a clean state. */}
      <motion.span
        key={burst}
        className={cn("flex", effect === "ring" && "origin-top")}
        initial={false}
        animate={on && burst > 0 && !reduce ? effects[effect] : { scale: 1, rotate: 0, y: 0 }}
        whileTap={{ scale: 0.8 }}
      >
        <Icon className={cn("size-5 transition-[fill] duration-200", on ? "fill-current" : "fill-transparent")} />
      </motion.span>
    </button>
  );
}

export default function ToggleLikeBookmarkDemo() {
  const [saved, setSaved] = useState(false);

  return (
    <article className="w-full max-w-sm rounded-2xl border border-border bg-card p-5">
      <p className="text-xs font-medium tracking-wide text-primary uppercase">Field notes</p>
      <h3 className="mt-2 font-serif text-xl leading-snug text-card-foreground">
        Designing motion that respects people&rsquo;s attention
      </h3>
      <p className="mt-2 text-sm text-muted-foreground">Tomás Brandt · 6 min read</p>
      <div className="mt-4 flex items-center gap-1 border-t border-border pt-3">
        <IconToggle icon={Heart} label="Like" defaultPressed />
        <IconToggle
          icon={Bookmark}
          label="Save"
          effect="drop"
          activeClassName="text-primary"
          pressed={saved}
          onPressedChange={setSaved}
        />
        <IconToggle icon={Star} label="Favourite" effect="spin" activeClassName="text-amber-400" />
        <IconToggle icon={Bell} label="Notify me about replies" effect="ring" activeClassName="text-chart-3" />
        <span className="ml-auto text-xs text-muted-foreground" aria-live="polite">
          {saved ? "Saved to reading list" : "Not saved"}
        </span>
      </div>
    </article>
  );
}
```

## AI prompt

```text
Add the "Toggle Like Bookmark" component from PixelPapa UI to this project.

What it is: Icon toggles that each fill in their own way: the heart pops, the bookmark drops in from above, the star spins and the bell rings, all with a ring burst.
Category: Toggles & Switches. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/toggle-like-bookmark.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/toggle-like-bookmark.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/toggle-like-bookmark" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More toggles & switches

- [Toggle Day Night](https://pixelpapa.com/components/toggles/toggle-day-night.md) (Pro): A theme switch drawn as a tiny sky: the sun rolls across and becomes a cratered moon, clouds sink away and stars pop out one by one.
- [Toggle Icon Morph](https://pixelpapa.com/components/toggles/toggle-icon-morph.md): A switch with a glyph inside the thumb: the two strokes of the cross swing and re-join into a check as the thumb rolls across.
- [Toggle iOS With Label List](https://pixelpapa.com/components/toggles/toggle-ios-with-label-list.md) (Pro): A settings card of labelled switches under a master toggle; turning the master off folds the dependent rows away with a staggered collapse.
- [Toggle Lock Shake](https://pixelpapa.com/components/toggles/toggle-lock-shake.md) (Pro): A switch that can be locked: clicking it then shakes its head, rattles the padlock in the thumb and pops up the reason it cannot change.
- [Toggle Segmented Theme](https://pixelpapa.com/components/toggles/toggle-segmented-theme.md) (Pro): A light / dark / system picker with a sliding thumb and icons that react when chosen, paired with a miniature window that wipes between themes and splits in two for "system".
- [Toggle Spring Switch](https://pixelpapa.com/components/toggles/toggle-spring-switch.md): A switch whose thumb stretches toward the other side while pressed, then snaps across with a little overshoot on release.
