Home/Components/Buttons/Animated Bookmark Button React Framer Motion
Back to search
Code · Live Preview
Componenttsx
import { Bookmark, BookmarkCheck } from "lucide-react";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";

export default function App() {
  const [isBookmarked, setIsBookmarked] = useState(false);

  function toggleBookmark() {
    setIsBookmarked((prev) => !prev);
  }

  return (
    <div className="flex items-center justify-center h-screen">
      <motion.button
        className="flex items-center justify-center py-2 px-4 w-fit border border-black/50 outline-none rounded-full bg-black cursor-pointer overflow-hidden"
        onClick={toggleBookmark}
        whileTap={{ scale: 0.95 }}
        whileHover={{ scale: 1.05 }}
        transition={{ type: "spring", stiffness: 400, damping: 17 }}
      >
        <AnimatePresence mode="wait" initial={false}>
          {isBookmarked ? (
            <motion.div
              key="bookmarked"
              className="flex items-center"
              initial={{ opacity: 0, y: 10, scale: 0.8 }}
              animate={{ opacity: 1, y: 0, scale: 1 }}
              exit={{ opacity: 0, y: -10, scale: 0.8 }}
              transition={{ duration: 0.2, ease: "easeOut" }}
            >
              <motion.div
                initial={{ rotate: -20, scale: 0.5 }}
                animate={{ rotate: 0, scale: 1 }}
                transition={{
                  type: "spring",
                  stiffness: 500,
                  damping: 15,
                  delay: 0.1,
                }}
              >
                <BookmarkCheck className="w-5 h-5 text-white" />
              </motion.div>
              <span className="mx-2 text-lg text-white">|</span>
              <span className="text-sm text-white">Bookmarked</span>
            </motion.div>
          ) : (
            <motion.div
              key="bookmark"
              className="flex items-center"
              initial={{ opacity: 0, y: 10, scale: 0.8 }}
              animate={{ opacity: 1, y: 0, scale: 1 }}
              exit={{ opacity: 0, y: -10, scale: 0.8 }}
              transition={{ duration: 0.2, ease: "easeOut" }}
            >
              <motion.div
                initial={{ rotate: 20, scale: 0.5 }}
                animate={{ rotate: 0, scale: 1 }}
                transition={{
                  type: "spring",
                  stiffness: 500,
                  damping: 15,
                  delay: 0.1,
                }}
              >
                <Bookmark className="w-5 h-5 text-white" />
              </motion.div>
              <span className="mx-2 text-lg text-white">|</span>
              <span className="text-sm text-white">Bookmark</span>
            </motion.div>
          )}
        </AnimatePresence>
      </motion.button>
    </div>
  );
}
Detecting libraries...

Component details

Overview, install command, dependencies, and project links.

An animated bookmark toggle button built with React, Tailwind CSS, and Framer Motion. Click it once - the icon swaps from Bookmark to BookmarkCheck with a satisfying spring animation. Click again - it snaps back. The icon entrance uses a combination of opacity, vertical slide, scale, and a subtle rotation spring so it never feels like a flat toggle. AnimatePresence with mode="wait" handles the exit animation before the new icon enters, so both never appear on screen at the same time. The button itself scales up slightly on hover and compresses on tap using whileHover and whileTap - all physics-based with spring easing. Icons are from Lucide React. Single component, zero prop drilling, state lives in one useState hook.

Added Jun 9, 2026

Discussion

Feedback, implementation tips, and usage notes.

0 threads

Comments

Sign in to join the conversation

Similar Components

More from the same category