Components
Loading preview...
Confetti animations are best used to delight your users when something special happens
npx shadcn@latest add https://21st.dev/r/dillionverma/confetti"use client"
import { useRef } from "react"
import confetti from "canvas-confetti"
import type { ConfettiRef } from "@/components/ui/confetti"
import { Confetti, ConfettiButton } from "@/components/ui/confetti"
import { Button } from "@/components/ui/button"
function ConfettiDemo() {
const confettiRef = useRef<ConfettiRef>(null)
return (
<div className="relative flex h-[500px] w-full flex-col items-center justify-center overflow-hidden rounded-lg border bg-background md:shadow-xl">
<span className="pointer-events-none whitespace-pre-wrap bg-gradient-to-b from-black to-gray-300/80 bg-clip-text text-center text-8xl font-semibold leading-none text-transparent dark:from-white dark:to-slate-900/10">
Confetti
</span>
<Confetti
ref={confettiRef}
className="absolute left-0 top-0 z-0 size-full"
onMouseEnter={() => {
confettiRef.current?.fire({})
}}
/>
</div>
)
}
function ConfettiButtonDemo() {
return (
<div className="relative">
<ConfettiButton>Confetti 🎉</ConfettiButton>
</div>
)
}
function ConfettiRandom() {
return (
<div className="relative">
<ConfettiButton
options={{
get angle() {
return Math.random() * 360
},
}}
>
Random Confetti 🎉
</ConfettiButton>
</div>
)
}
function ConfettiFireworks() {
const handleClick = () => {
const duration = 5 * 1000
const animationEnd = Date.now() + duration
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 }
const randomInRange = (min: number, max: number) =>
Math.random() * (max - min) + min
const interval = window.setInterval(() => {
const timeLeft = animationEnd - Date.now()
if (timeLeft <= 0) {
return clearInterval(interval)
}
const particleCount = 50 * (timeLeft / duration)
confetti({
...defaults,
particleCount,
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
})
confetti({
...defaults,
particleCount,
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
})
}, 250)
}
return (
<div className="relative">
<Button onClick={handleClick}>Trigger Fireworks</Button>
</div>
)
}
function ConfettiSideCannons() {
const handleClick = () => {
const end = Date.now() + 3 * 1000 // 3 seconds
const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"]
const frame = () => {
if (Date.now() > end) return
confetti({
particleCount: 2,
angle: 60,
spread: 55,
startVelocity: 60,
origin: { x: 0, y: 0.5 },
colors: colors,
})
confetti({
particleCount: 2,
angle: 120,
spread: 55,
startVelocity: 60,
origin: { x: 1, y: 0.5 },
colors: colors,
})
requestAnimationFrame(frame)
}
frame()
}
return (
<div className="relative">
<Button onClick={handleClick}>Trigger Side Cannons</Button>
</div>
)
}
function ConfettiStars() {
const handleClick = () => {
const defaults = {
spread: 360,
ticks: 50,
gravity: 0,
decay: 0.94,
startVelocity: 30,
colors: ["#FFE400", "#FFBD00", "#E89400", "#FFCA6C", "#FDFFB8"],
}
const shoot = () => {
confetti({
...defaults,
particleCount: 40,
scalar: 1.2,
shapes: ["star"],
})
confetti({
...defaults,
particleCount: 10,
scalar: 0.75,
shapes: ["circle"],
})
}
setTimeout(shoot, 0)
setTimeout(shoot, 100)
setTimeout(shoot, 200)
}
return (
<div className="relative">
<Button onClick={handleClick}>Trigger Stars</Button>
</div>
)
}
function ConfettiCustomShapes() {
const handleClick = () => {
const scalar = 2
const triangle = confetti.shapeFromPath({
path: "M0 10 L5 0 L10 10z",
})
const square = confetti.shapeFromPath({
path: "M0 0 L10 0 L10 10 L0 10 Z",
})
const coin = confetti.shapeFromPath({
path: "M5 0 A5 5 0 1 0 5 10 A5 5 0 1 0 5 0 Z",
})
const tree = confetti.shapeFromPath({
path: "M5 0 L10 10 L0 10 Z",
})
const defaults = {
spread: 360,
ticks: 60,
gravity: 0,
decay: 0.96,
startVelocity: 20,
shapes: [triangle, square, coin, tree],
scalar,
}
const shoot = () => {
confetti({
...defaults,
particleCount: 30,
})
confetti({
...defaults,
particleCount: 5,
})
confetti({
...defaults,
particleCount: 15,
scalar: scalar / 2,
shapes: ["circle"],
})
}
setTimeout(shoot, 0)
setTimeout(shoot, 100)
setTimeout(shoot, 200)
}
return (
<div className="relative flex items-center justify-center">
<Button onClick={handleClick}>Trigger Shapes</Button>
</div>
)
}
function ConfettiEmoji() {
const handleClick = () => {
const scalar = 2
const unicorn = confetti.shapeFromText({ text: "🦄", scalar })
const defaults = {
spread: 360,
ticks: 60,
gravity: 0,
decay: 0.96,
startVelocity: 20,
shapes: [unicorn],
scalar,
}
const shoot = () => {
confetti({
...defaults,
particleCount: 30,
})
confetti({
...defaults,
particleCount: 5,
})
confetti({
...defaults,
particleCount: 15,
scalar: scalar / 2,
shapes: ["circle"],
})
}
setTimeout(shoot, 0)
setTimeout(shoot, 100)
setTimeout(shoot, 200)
}
return (
<div className="relative justify-center">
<Button onClick={handleClick}>Trigger Emoji</Button>
</div>
)
}
export {
ConfettiDemo,
ConfettiButtonDemo,
ConfettiRandom,
ConfettiFireworks,
ConfettiSideCannons,
ConfettiStars,
ConfettiCustomShapes,
ConfettiEmoji,
}