Skip to main content

Your First Sound

Let’s create a button with audio feedback in just a few steps.
1

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/
2

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>
  );
}
3

Test It Out

Import and use your component:
src/app/page.tsx
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

1

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 />;
}
2

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";
3

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

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
});
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>
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.

Build docs developers (and LLMs) love