Your First Sound
Let’s create a button with audio feedback in just a few steps.
Install a Sound
First, install a click sound from the Soundcn registry: npx shadcn add https://soundcn.xyz/r/click-soft.json
This installs:
The sound file in src/sounds/click-soft/
The useSound hook in src/hooks/
Supporting utilities in src/lib/
Import and Use
Create a component that plays the sound on click: src/components/sound-button.tsx
"use client" ; // Next.js App Router only
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
export function SoundButton () {
const [ play ] = useSound ( clickSoftSound );
return (
< button
onClick = { play }
className = "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Click me for sound!
</ button >
);
}
Test It Out
Import and use your component: import { SoundButton } from "@/components/sound-button" ;
export default function Home () {
return (
< main className = "flex min-h-screen items-center justify-center" >
< SoundButton />
</ main >
);
}
Click the button and hear your sound!
Basic Usage Patterns
Simple Click Sound
The most basic usage - play a sound on button click:
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
function Button () {
const [ play ] = useSound ( clickSoftSound );
return < button onClick = { play } > Click </ button > ;
}
With Volume Control
Adjust the volume (0 to 1):
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
function QuietButton () {
const [ play ] = useSound ( clickSoftSound , {
volume: 0.3 , // 30% volume
});
return < button onClick = { play } > Quiet Click </ button > ;
}
Dynamic Volume
Override volume at play time:
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
import { useState } from "react" ;
function VolumeSlider () {
const [ volume , setVolume ] = useState ( 0.5 );
const [ play ] = useSound ( clickSoftSound );
return (
< div >
< input
type = "range"
min = "0"
max = "1"
step = "0.1"
value = { volume }
onChange = { ( e ) => setVolume ( Number ( e . target . value )) }
/>
< button onClick = { () => play ({ volume }) } >
Test Volume
</ button >
</ div >
);
}
Playback Rate (Speed)
Change the playback speed:
import { useSound } from "@/hooks/use-sound" ;
import { notificationSound } from "@/sounds/notification-pop" ;
function SpeedTest () {
const [ play ] = useSound ( notificationSound );
return (
< div className = "flex gap-2" >
< button onClick = { () => play ({ playbackRate: 0.5 }) } >
Slow (0.5x)
</ button >
< button onClick = { () => play ({ playbackRate: 1 }) } >
Normal
</ button >
< button onClick = { () => play ({ playbackRate: 1.5 }) } >
Fast (1.5x)
</ button >
</ div >
);
}
Play State Tracking
Monitor whether a sound is currently playing:
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
function PlayingIndicator () {
const [ play , { isPlaying }] = useSound ( clickSoftSound );
return (
< button onClick = { play } >
{ isPlaying ? "🔊 Playing..." : "▶️ Play Sound" }
</ button >
);
}
Stop and Pause
Control playback with stop and pause:
import { useSound } from "@/hooks/use-sound" ;
import { longAmbienceSound } from "@/sounds/long-ambience" ;
function AudioControls () {
const [ play , { stop , pause , isPlaying }] = useSound ( longAmbienceSound );
return (
< div className = "flex gap-2" >
< button onClick = { play } disabled = { isPlaying } >
Play
</ button >
< button onClick = { pause } disabled = { ! isPlaying } >
Pause
</ button >
< button onClick = { stop } disabled = { ! isPlaying } >
Stop
</ button >
</ div >
);
}
Advanced Examples
Event Callbacks
Respond to playback events:
import { useSound } from "@/hooks/use-sound" ;
import { notificationSound } from "@/sounds/notification-pop" ;
import { useState } from "react" ;
function NotificationWithFeedback () {
const [ message , setMessage ] = useState ( "" );
const [ play ] = useSound ( notificationSound , {
onPlay : () => setMessage ( "🔊 Playing..." ),
onEnd : () => setMessage ( "✅ Finished!" ),
onStop : () => setMessage ( "⏹️ Stopped" ),
});
return (
< div >
< button onClick = { play } > Play Notification </ button >
{ message && < p > { message } </ p > }
</ div >
);
}
User Preference Toggle
Respect user sound preferences:
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
import { useState } from "react" ;
function SoundToggle () {
const [ soundEnabled , setSoundEnabled ] = useState ( true );
const [ play ] = useSound ( clickSoftSound , { soundEnabled });
return (
< div className = "flex flex-col gap-4" >
< label className = "flex items-center gap-2" >
< input
type = "checkbox"
checked = { soundEnabled }
onChange = { ( e ) => setSoundEnabled ( e . target . checked ) }
/>
Enable Sounds
</ label >
< button onClick = { play } >
{ soundEnabled ? "🔊 Click with sound" : "🔇 Click (muted)" }
</ button >
</ div >
);
}
Interrupt Previous Playback
Prevent overlapping sounds:
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
function RapidFireButton () {
const [ play ] = useSound ( clickSoftSound , {
interrupt: true , // Stop previous playback before starting new one
});
return (
< button onClick = { play } >
Rapid Click (No Overlap)
</ button >
);
}
Multiple Sounds in One Component
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
import { notificationSound } from "@/sounds/notification-pop" ;
import { errorSound } from "@/sounds/error-beep" ;
import { useState } from "react" ;
function FormWithSounds () {
const [ playClick ] = useSound ( clickSoftSound );
const [ playSuccess ] = useSound ( notificationSound );
const [ playError ] = useSound ( errorSound );
const [ email , setEmail ] = useState ( "" );
const handleSubmit = ( e : React . FormEvent ) => {
e . preventDefault ();
playClick ();
if ( email . includes ( "@" )) {
playSuccess ();
alert ( "Success!" );
} else {
playError ();
alert ( "Invalid email" );
}
};
return (
< form onSubmit = { handleSubmit } className = "flex flex-col gap-4" >
< input
type = "email"
value = { email }
onChange = { ( e ) => setEmail ( e . target . value ) }
placeholder = "Enter email"
className = "px-4 py-2 border rounded"
/>
< button
type = "submit"
className = "px-4 py-2 bg-blue-500 text-white rounded"
>
Submit
</ button >
</ form >
);
}
Framework-Agnostic Usage
If you’re not using React or need to play sounds outside of components, use the playSound function:
import { playSound } from "@/lib/sound-engine" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
// Play immediately
await playSound ( clickSoftSound . dataUri );
// With options
const playback = await playSound ( clickSoftSound . dataUri , {
volume: 0.5 ,
playbackRate: 1.2 ,
onEnd : () => console . log ( "Finished!" ),
});
// Stop manually
playback . stop ();
The playSound function works in any JavaScript environment, not just React. Use it in vanilla JS, Vue, Svelte, Angular, or any other framework.
Common Use Cases
Button Feedback
import { useSound } from "@/hooks/use-sound" ;
import { clickSoftSound } from "@/sounds/click-soft" ;
export function ActionButton ({ onClick , children } : any ) {
const [ play ] = useSound ( clickSoftSound , { volume: 0.4 });
const handleClick = () => {
play ();
onClick ?.();
};
return (
< button onClick = { handleClick } >
{ children }
</ button >
);
}
Notification Sound
import { useSound } from "@/hooks/use-sound" ;
import { notificationSound } from "@/sounds/notification-pop" ;
import { useEffect } from "react" ;
export function NotificationToast ({ message , show } : any ) {
const [ play ] = useSound ( notificationSound );
useEffect (() => {
if ( show ) {
play ();
}
}, [ show , play ]);
if ( ! show ) return null ;
return (
< div className = "fixed top-4 right-4 bg-black text-white p-4 rounded" >
{ message }
</ div >
);
}
Toggle Switch Sound
import { useSound } from "@/hooks/use-sound" ;
import { switchOnSound } from "@/sounds/switch-on" ;
import { switchOffSound } from "@/sounds/switch-off" ;
import { useState } from "react" ;
export function SoundToggleSwitch () {
const [ enabled , setEnabled ] = useState ( false );
const [ playOn ] = useSound ( switchOnSound );
const [ playOff ] = useSound ( switchOffSound );
const toggle = () => {
setEnabled ( prev => {
const newValue = ! prev ;
newValue ? playOn () : playOff ();
return newValue ;
});
};
return (
< button
onClick = { toggle }
className = { `w-12 h-6 rounded-full transition ${
enabled ? 'bg-blue-500' : 'bg-gray-300'
} ` }
>
< div className = { `w-5 h-5 bg-white rounded-full transition-transform ${
enabled ? 'translate-x-6' : 'translate-x-1'
} ` } />
</ button >
);
}
import { useSound } from "@/hooks/use-sound" ;
import { hoverSound } from "@/sounds/hover-soft" ;
const menuItems = [ "Home" , "About" , "Products" , "Contact" ];
export function SoundMenu () {
const [ play ] = useSound ( hoverSound , { volume: 0.2 });
return (
< nav className = "flex gap-4" >
{ menuItems . map ( item => (
< a
key = { item }
href = { `# ${ item . toLowerCase () } ` }
onMouseEnter = { play }
className = "hover:text-blue-500 transition"
>
{ item }
</ a >
)) }
</ nav >
);
}
Performance Tips
Preload Critical Sounds
The useSound hook automatically preloads and caches audio buffers when the component mounts. For critical sounds, mount components early: // Preload sounds in a top-level component
function App () {
useSound ( clickSoftSound ); // Preloads immediately
return < YourApp /> ;
}
Tree-Shaking
Only import sounds you use. Unused sounds won’t be included in your bundle: // ✅ Only click-soft is bundled
import { clickSoftSound } from "@/sounds/click-soft" ;
// ❌ Don't import barrel exports of all sounds
import * as sounds from "@/sounds" ;
Buffer Caching
Audio buffers are automatically cached. Multiple components using the same sound share the same decoded audio data: // Both components share the same cached audio buffer
function Button1 () {
const [ play ] = useSound ( clickSoftSound );
return < button onClick = { play } > Button 1 </ button > ;
}
function Button2 () {
const [ play ] = useSound ( clickSoftSound );
return < button onClick = { play } > Button 2 </ button > ;
}
Base64-encoded sounds increase your JavaScript bundle size. Each sound typically adds 1-5 KB. For very large sound libraries (50+ sounds), consider code-splitting or lazy loading components.
Troubleshooting
Sound doesn't play on first click (mobile)
Mobile browsers require user interaction to unlock audio. The first click initializes the audio context: const [ play ] = useSound ( clickSoftSound );
// First click may not play on mobile - this is expected
// Subsequent clicks will work
< button onClick = { play } > Click me </ button >
You can add a “Start” button to explicitly initialize audio: const [ initialized , setInitialized ] = useState ( false );
const [ play ] = useSound ( clickSoftSound );
if ( ! initialized ) {
return (
< button onClick = { () => setInitialized ( true ) } >
Enable Sound
</ button >
);
}
Use the interrupt option to prevent overlapping: const [ play ] = useSound ( clickSoftSound , {
interrupt: true , // Stops previous playback
});
Sound plays twice on click
Make sure you’re not calling play() twice. Pass the function reference, don’t call it: // ✅ Correct
< button onClick = { play } > Click </ button >
// ❌ Wrong - plays immediately on render
< button onClick = { play () } > Click </ button >
Can't hear very short sounds
Very short sounds (< 50ms) may be hard to hear. Consider:
Increasing volume: volume: 1
Choosing a different sound variant
Testing on different devices/speakers
Next Steps
Now that you know the basics:
Browse the Sound Library - Discover 700+ sounds
Explore the API Reference for advanced options
Check out the TypeScript types for full IntelliSense support
Have questions or feedback? Visit the GitHub repository to open an issue or contribute.