Loading to check pill
A pill button that shows a spinner while it works, then pops a check mark into place with a spring.
Settings
Code
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Check, Loader2 } from "lucide-react";
const spring = { type: "spring", stiffness: 300, damping: 30 } as const;
interface LoadingCheckButtonProps {
text?: string;
color?: string;
/** Duración de la carga simulada en ms. */
delay?: number;
}
export default function LoadingCheckButton({
text = "Book now",
color = "#0071e3",
delay = 1200,
}: LoadingCheckButtonProps) {
const [state, setState] = useState<"idle" | "load" | "done">("idle");
const go = () => {
if (state !== "idle") return;
setState("load");
setTimeout(() => setState("done"), delay);
setTimeout(() => setState("idle"), delay + 1600);
};
return (
<motion.button
type="button"
whileTap={{ scale: 0.96 }}
onClick={go}
className="flex min-w-36 items-center justify-center gap-2 rounded-full px-6 py-3 text-sm font-medium text-white shadow-[0_1px_2px_rgba(0,0,0,0.03),0_8px_28px_rgba(0,0,0,0.05)]"
style={{ backgroundColor: color }}
>
<AnimatePresence mode="wait" initial={false}>
{state === "idle" ? (
<motion.span
key="idle"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{text}
</motion.span>
) : state === "load" ? (
<motion.span key="load" initial={{ scale: 0.6 }} animate={{ scale: 1 }}>
<Loader2 className="h-4 w-4 animate-spin" />
</motion.span>
) : (
<motion.span
key="done"
initial={{ scale: 0.4, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={spring}
className="flex items-center gap-2"
>
<Check className="h-4 w-4" /> Done
</motion.span>
)}
</AnimatePresence>
</motion.button>
);
}
Prompt for AI
Paste it into ChatGPT, Claude or Cursor and it will adapt the block to your project with these settings, even if you don’t use React.
More in Buttons
Where to use it
Some actions deserve a clear “it’s done”: booking a table, paying for an order, reserving a class at the gym. This pill runs through a short wait and ends with a check that bounces in, so nobody wonders whether it went through. That final bounce turns a plain loading button into a success animation people actually notice.
Set the duration close to how long your request really takes, usually under a second and a half. If it can take much longer, add a line of text such as “Confirming…” next to it so the wait does not feel stuck.