Componentform
Wishlist Button
Heart wishlist button with pop fill and floating burst.
Preview
Live Preview

25%
Premium Headphones
$299$399
Jordan Daisy
Product Designer
AvailableRemote

The Future of Design Systems
Alex Rivera8 min read
Oc
t 14
Tech Conference 2026
21:00
Berlin Arena
Invoice #INV-2026-0987draft
Date: 2026-09-15Due: 2026-10-15
| Item | Qty | Price |
|---|---|---|
| Design Services | 1 | $150 |
| Development Hours | 5 | $100 |
SubtotalNaN
Tax (10%)NaN
TotalNaN
THERMAL RECEIPT#8M213D
Design Consultation$150
Development Hours$200
Total$385.00
Thank you for your purchase!
Subtotal$377.00
Tax$30.16
Total$407.16
Color
Size
✓✓✓✓✓✓
Order #102938Processing
Design Services1x
Development5x
VIP
LOYALTY TIER
$100
Jordan Daisy
Happy Birthday!
J
Jordan Daisy
Product Designer
UX
Badge: EMP-8942
Wedding Reception
Please confirm your attendance
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add wishlist-buttonOr install the full npm package:
npm install bigbulluiUsage
import { WishlistButton } from "@/components/ui/wishlist-button";
<WishlistButton />Source code
wishlist-button.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface WishlistButtonProps {
active?: boolean;
onToggle?: (active: boolean) => void;
size?: "sm" | "md" | "lg";
className?: string;
}
export function WishlistButton({
active = false,
onToggle,
size = "md",
className,
}: WishlistButtonProps) {
const [isActive, setIsActive] = React.useState(active);
const handleToggle = () => {
setIsActive(!isActive);
onToggle?.(!isActive);
};
const sizeClass = size === "sm" ? "size-4" : size === "lg" ? "size-6" : "size-5";
return (
<>
<button
onClick={handleToggle}
className={cn(
"inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-full border-2 border-foreground bg-card text-foreground transition-all duration-200 hover:scale-105 active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring mb-2",
active && "bg-accent text-accent",
!active && "hover:bg-accent hover:text-accent-foreground",
className
)}
>
<svg
width={sizeClass}
height={sizeClass}
viewBox="0 0 24 24"
fill={isActive ? "currentColor" : "none"}
aria-hidden="true"
className={cn("transition-transform duration-200")}
>
<path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 5 16.5 5 17.33 5 19 6.34 19 8.5 0 3.43-.79 1.42-2.19-1.11L12 21.35z"
/>
</svg>
Wishlist
</button>
{/* Floating mini hearts burst - only when active */}
{isActive && (
<div
className={cn(
"absolute inset-0 pointer-events-none overflow-hidden motion-reduce:animate-none"
)}
>
{Array.from({ length: 6 }).map((_, i) => (
<span
key={i}
className="absolute text-accent text-xs animate-[wishlistBurst_0.6s_ease-out_both] motion-reduce:animate-none"
style={{ left: `${10 + i * 15}%`, animationDelay: `${i * 60}ms` }}
>
✓
</span>
))}
</div>
)}
<style>{`
@keyframes wishlist-heart-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.3); }
}
@keyframes wishlist-floating-hearts {
0% { transform: translateY(0) scale(1); opacity: 1; }
100% { transform: translateY(-100px) scale(0.8); opacity: 0; }
}
`}</style>
</>
);
}Props
| Prop | Type | Description |
|---|---|---|
| onToggle | (active: boolean) => void | Wishlist toggle callback. |
| size | "sm" | "md" | "lg" | Button size (default 'md'). |