A lightweight, performant React library for adding delightful animation effects to your applications. Perfect for celebrating user achievements, form submissions, or any moment worth highlighting.
→ Try the Live Demo
🎯 Simple API - Just one hook to rule them all
🎨 11 Beautiful Animations - Confetti, sparkles, hearts, stars, fireworks, bubbles, snow, emoji, coins, lightning, and petals
📦 Tiny Bundle - Zero dependencies, optimized for performance
🚀 Performant - Optimized animations using requestAnimationFrame
🎮 Full Control - Customize colors, particle count, physics, and more
📱 Responsive - Works seamlessly on all devices
🔧 TypeScript Support - Fully typed for excellent DX
⚡ React 18 Ready - Built with the latest React features
npm install partycles
# or
yarn add partycles
# or
pnpm add partycles
import { useReward } from 'partycles' ;
function App ( ) {
const { reward, isAnimating } = useReward ( 'rewardId' , 'confetti' ) ;
return (
< button id = "rewardId" onClick = { reward } disabled = { isAnimating } >
Click me for a surprise! 🎉
</ button >
) ;
}
That's it! No configuration needed - it just works. 🎊
Zero Dependencies - No bloat, just pure React code
One Hook - Simple useReward hook handles everything
11 Animations - From confetti to lightning, we've got you covered
Fully Typed - Great TypeScript support out of the box
Customizable - Tweak colors, physics, particle count, and more
Performant - Optimized animations that won't slow down your app
useReward(elementId, animationType, config?)
The main hook for creating reward animations.
elementId (string): The ID of the element to animate from
animationType (string): One of: 'confetti', 'sparkles', 'hearts', 'stars', 'fireworks', 'bubbles', 'snow', 'emoji', 'coins', 'lightning', 'petals'
config (optional): Animation configuration object
reward (function): Triggers the animation
isAnimating (boolean): Whether the animation is currently running
interface AnimationConfig {
particleCount ?: number ; // Number of particles (default: varies by animation)
spread ?: number ; // Spread angle in degrees (default: 50)
startVelocity ?: number ; // Initial velocity (default: 45)
decay ?: number ; // Velocity decay rate (default: 0.9)
lifetime ?: number ; // Particle lifetime (default: 100)
colors ?: string [ ] ; // Array of colors (or emojis for 'emoji' type)
elementSize ?: number ; // Size of particles in pixels
duration ?: number ; // Total animation duration
physics ?: {
gravity ?: number ; // Gravity force (default: 0.5)
wind ?: number ; // Horizontal wind force (default: 0)
friction ?: number ; // Air friction (default: 0.99)
} ;
}
Classic celebration effect with colorful paper pieces.
const { reward } = useReward ( 'buttonId' , 'confetti' , {
particleCount : 30 ,
spread : 60 ,
colors : [ '#ff0000' , '#00ff00' , '#0000ff' ]
} ) ;
Twinkling star effect perfect for magical moments.
const { reward } = useReward ( 'buttonId' , 'sparkles' , {
particleCount : 20 ,
elementSize : 15
} ) ;
Floating hearts for likes, favorites, or love actions.
const { reward } = useReward ( 'buttonId' , 'hearts' , {
particleCount : 15 ,
colors : [ '#ff1744' , '#e91e63' , '#ff4569' ]
} ) ;
Shooting stars effect for achievements and ratings.
const { reward } = useReward ( 'buttonId' , 'stars' , {
particleCount : 25 ,
physics : { gravity : 0.3 }
} ) ;
Explosive celebration for major milestones.
const { reward } = useReward ( 'buttonId' , 'fireworks' , {
particleCount : 40 ,
spread : 180
} ) ;
Gentle floating bubbles for calm, playful effects.
const { reward } = useReward ( 'buttonId' , 'bubbles' , {
particleCount : 20 ,
physics : { gravity : - 0.1 }
} ) ;
Peaceful falling snowflakes for winter themes.
const { reward } = useReward ( 'buttonId' , 'snow' , {
particleCount : 50 ,
physics : { gravity : 0.05 , wind : 0.1 }
} ) ;
Customizable emoji explosions for any mood.
import { useReward , emojiPresets } from 'partycles' ;
// Use built-in presets
const { reward } = useReward ( 'buttonId' , 'emoji' , {
colors : emojiPresets . celebration // 🎉🎊🥳🎈🎁🍾🥂🎆
} ) ;
// Or custom emojis
const { reward } = useReward ( 'buttonId' , 'emoji' , {
particleCount : 30 ,
colors : [ '🎉' , '🎊' , '🎈' , '🎁' , '✨' ]
} ) ;
Available emoji presets:
emojiPresets.celebration - Party emojis
emojiPresets.love - Heart emojis
emojiPresets.happy - Happy face emojis
emojiPresets.nature - Nature emojis
emojiPresets.food - Food emojis
Falling coins for rewards and achievements.
const { reward } = useReward ( 'buttonId' , 'coins' , {
particleCount : 25 ,
physics : { gravity : 0.5 }
} ) ;
Electric energy bursts for powerful actions.
const { reward } = useReward ( 'buttonId' , 'lightning' , {
particleCount : 20 ,
spread : 360
} ) ;
Delicate flower petals floating in the wind.
const { reward } = useReward ( 'buttonId' , 'petals' , {
particleCount : 40 ,
physics : { gravity : 0.08 , wind : 0.15 }
} ) ;
function ContactForm ( ) {
const { reward } = useReward ( 'submit-btn' , 'confetti' ) ;
const handleSubmit = async ( e ) => {
e . preventDefault ( ) ;
const success = await submitForm ( ) ;
if ( success ) {
reward ( ) ;
}
} ;
return (
< form onSubmit = { handleSubmit } >
{ /* form fields */ }
< button id = "submit-btn" type = "submit" >
Send Message
</ button >
</ form >
) ;
}
function LikeButton ( ) {
const [ liked , setLiked ] = useState ( false ) ;
const { reward } = useReward ( 'like-btn' , 'hearts' , {
particleCount : 10 ,
elementSize : 20
} ) ;
const handleLike = ( ) => {
setLiked ( ! liked ) ;
if ( ! liked ) {
reward ( ) ;
}
} ;
return (
< button id = "like-btn" onClick = { handleLike } >
{ liked ? '❤️' : '🤍' } Like
</ button >
) ;
}
function Achievement ( { unlocked, name } ) {
const { reward } = useReward ( 'achievement' , 'fireworks' , {
particleCount : 50 ,
spread : 120
} ) ;
useEffect ( ( ) => {
if ( unlocked ) {
reward ( ) ;
}
} , [ unlocked , reward ] ) ;
return (
< div id = "achievement" className = "achievement-badge" >
🏆 { name }
</ div >
) ;
}
Unique IDs : Ensure each animated element has a unique ID
Performance : Avoid triggering multiple animations simultaneously
Accessibility : Provide alternative feedback for users who prefer reduced motion
Mobile : Test animations on mobile devices and adjust particle counts if needed
// Respect user preferences
const prefersReducedMotion = window . matchMedia ( '(prefers-reduced-motion: reduce)' ) . matches ;
const { reward } = useReward ( 'buttonId' , 'confetti' , {
particleCount : prefersReducedMotion ? 0 : 30
} ) ;
const { reward } = useReward ( 'buttonId' , 'confetti' , {
physics : {
gravity : 0.2 , // Lower gravity = floatier particles
wind : 0.1 , // Positive = blow right, negative = blow left
friction : 0.95 // Higher = more air resistance
}
} ) ;
function DynamicReward ( ) {
const [ intensity , setIntensity ] = useState ( 1 ) ;
const { reward } = useReward ( 'buttonId' , 'confetti' , {
particleCount : 30 * intensity ,
spread : 50 + ( 20 * intensity ) ,
startVelocity : 45 * intensity
} ) ;
return (
< >
< input
type = "range"
min = "0.5"
max = "2"
step = "0.1"
value = { intensity }
onChange = { ( e ) => setIntensity ( parseFloat ( e . target . value ) ) }
/>
< button id = "buttonId" onClick = { reward } >
Celebrate!
</ button >
</ >
) ;
}
We welcome contributions! Please see our Contributing Guide for details.
Fork the repository
Create your feature branch (git checkout -b feature/amazing-feature)
Commit your changes (git commit -m 'Add amazing feature')
Push to the branch (git push origin feature/amazing-feature)
Open a Pull Request
MIT © Jonathan Leane
Made with ❤️ by developers, for developers