This is an example project build in top of the boilerplate template Ui Component Library by BoilerplateHQ.
What you see is what you get. Meaning, this is exactly what you get when you clone the repository and run the project.
Confetti Button
Beautifully designed button that makes it rain confetti!
Preview
This is how the code looks like in action.
Step-By-Step Guide
1. Install Dependencies
Toast by Sonner - Follow instructions by shadcn/ui - toast
cli
1npm install sonner
Button component by shadcn/ui - button
cli
1npx shadcn-ui@latest add button
2. Copy the Source Code
Confetti
@/ui/Confetti.tsx
1'use client';
2
3// Credit to: BoilerplateHQ.com
4// Import Types
5// Import External Packages
6import { useEffect, useRef } from 'react';
7// Import Components
8// Import Functions & Actions & Hooks & State
9// Import Data
10// Import Assets & Icons
11
12interface ConfettiParticle {
13 color: string; // RGB color string
14 x: number; // x-position
15 y: number; // y-position
16 diameter: number; // Size of the particle
17 tilt: number; // Tilt angle
18 tiltAngleIncrement: number; // Tilt angle increment per frame
19 tiltAngle: number; // Current tilt angle
20 particleSpeed: number; // Speed of the particle
21 waveAngle: number; // Angle for the wave movement
22 waveAngleIncrement: number; // Wave angle increment per frame
23 opacity: number; // Opacity of the particle
24}
25
26const generateConfettiParticles = (count: number): ConfettiParticle[] => {
27 const particles: ConfettiParticle[] = [];
28 for (let i = 0; i < count; i++) {
29 particles.push({
30 color: `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`,
31 x: Math.random() * window.innerWidth,
32 y: Math.random() * window.innerHeight - window.innerHeight,
33 diameter: Math.random() * 10 + 1,
34 tilt: Math.random() * 10 - 10,
35 tiltAngleIncrement: Math.random() * 0.07 + 0.05,
36 tiltAngle: 0,
37 particleSpeed: Math.random() * 10 + 1,
38 waveAngle: 0,
39 waveAngleIncrement: Math.random() * 0.1 + 0.05,
40 opacity: 1,
41 });
42 }
43 return particles;
44};
45
46const updateConfettiParticles = (
47 particles: ConfettiParticle[],
48 duration: number
49): ConfettiParticle[] => {
50 const opacityDecrement = 1 / (60 * (duration / 1000));
51 return particles
52 .map((particle) => ({
53 ...particle,
54 x: particle.x + Math.sin(particle.waveAngle) * 2, // Horizontal movement
55 y: particle.y + particle.particleSpeed, // Vertical movement due to gravity
56 tiltAngle: particle.tiltAngle + particle.tiltAngleIncrement,
57 tilt: particle.tilt + Math.sin(particle.tiltAngle) * 12, // Tilt
58 waveAngle: particle.waveAngle + particle.waveAngleIncrement, // Wave movement
59 opacity: Math.max(0, particle.opacity - opacityDecrement),
60 }))
61 .filter((particle) => particle.y < window.innerHeight); // Keep particles within the canvas
62};
63
64/**
65 * Renders a confetti animation on a canvas element.
66 * @param isActive - Determines whether the confetti animation is active.
67 * @param duration - The duration of the confetti animation in milliseconds.
68 * @returns The Confetti component.
69 */
70export default function Confetti({
71 isActive,
72 duration,
73}: {
74 isActive: boolean;
75 duration: number;
76}) {
77 const canvasRef = useRef<HTMLCanvasElement>(null);
78
79 useEffect(() => {
80 if (!isActive || !canvasRef.current) return;
81
82 const canvas = canvasRef.current;
83 const ctx = canvas.getContext('2d');
84 if (!ctx) return;
85
86 canvas.width = window.innerWidth;
87 canvas.height = window.innerHeight;
88
89 let particles = generateConfettiParticles(1000);
90 let animationFrameId: number;
91
92 const animate = () => {
93 ctx.clearRect(0, 0, canvas.width, canvas.height);
94 particles = updateConfettiParticles(particles, duration);
95
96 // Draw particles
97 particles.forEach((particle) => {
98 ctx.beginPath();
99 ctx.arc(particle.x, particle.y, particle.diameter / 2, 0, 2 * Math.PI);
100 ctx.fillStyle = particle.color;
101 ctx.globalAlpha = particle.opacity;
102 ctx.fill();
103 });
104 ctx.globalAlpha = 1;
105
106 animationFrameId = requestAnimationFrame(animate);
107 };
108
109 animate();
110
111 const timeoutId = setTimeout(() => {
112 cancelAnimationFrame(animationFrameId);
113 }, duration);
114
115 return () => {
116 clearTimeout(timeoutId);
117 cancelAnimationFrame(animationFrameId);
118 };
119 }, [isActive, duration]);
120
121 return (
122 <canvas
123 ref={canvasRef}
124 className="h-lvh w-full absolute"
125 style={{
126 pointerEvents: 'none',
127 zIndex: 9999,
128 }}
129 />
130 );
131}
Button
@/component/ConfettiButton.tsx
1'use client';
2
3// Import Types
4// Import External Packages
5import { useState } from 'react';
6import { toast } from 'sonner';
7// Import Components
8import { Button } from '@/ui/Button';
9import Confetti from '@/ui/Confetti';
10// Import Functions & Actions & Hooks & State
11// Import Data
12// Import Assets & Icons
13
14export default function ConfettiButton() {
15 const [isConfettiActive, setIsConfettiActive] = useState(false);
16 return (
17 <>
18 <Button
19 onClick={() => {
20 toast.success(
21 'Oh WOW! You actually clicked the button! See ... it is well-designed! As gratitude, enjoy some confetti. 🎉'
22 );
23
24 setTimeout(() => {
25 setIsConfettiActive(true);
26 }, 3000);
27
28 setTimeout(() => {
29 setIsConfettiActive(false);
30 }, 6000);
31 }}
32 variant="default"
33 size="lg"
34 className="hover:bg-primary/80 animate-pulse shadow-2xl shadow-neutral-500"
35 >
36 Click Me
37 </Button>
38 <Confetti isActive={isConfettiActive} duration={3000} />
39 </>
40 );
41}
3. Use in your App
page.tsx
1export default function Page() {
2 return (
3 <div className='relative dark:bg-black max-w-5xl mx-auto'>
4 <div className="mx-auto max-w-7xl py-16">
5 <div className="mx-auto text-center">
6 <h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-6xl max-w-2xl mx-auto">
7 Buttons Rule The World
8 </h1>
9
10 <p className="mt-6 text-lg leading-8 text-muted-foreground dark:text-zinc-200 max-w-4xl mx-auto">
11 We knead the code and bake the pixels to perfection to serve you the
12 most delightful online buttons - LIKE, SUBMIT, and everything in
13 between.
14 </p>
15 <div className="mt-10 flex items-center justify-center gap-x-6">
16 <ConfettiButton />
17 </div>
18 </div>
19 </div>
20 </div>
21 );
22}
Tech Stack
Only the finest ingredients are used in our products. We made sure that we only use the best technologies available which offer a free tier! Yes, you read that right. Everything can be set up for USD 0. The only thing you will need is a domain name.
Example Usage
See this component live in action. Are you using the component on your website? Shoot us an email (support@boilerplatehq.com) and we will link to you.
BoilerplateHQ
BoilerplateHQ is using the confetti effect after someone made a purchase!
Your Website?
Are you using this component? Shoot us an email and we will link to you.
Frequently Asked Questions
We have compiled a list of frequently asked questions. If you have any other questions, please do not hesitate to contact us via email or using the chat function. We are here to help!
What are you waiting for?
Button up and get started with this amazing product!
Excited about buttons as we are?
Get the latest buttons and button news straight to your inbox.
DISCLAIMER: This is the Newsletter of BoilerplateHQ! Powered by Beehiiv.com. Unsubscribe at anytime by clicking the unsubscribe button in any email sent to you. Read our privacy policy