Enhance your notification system with context-appropriate sounds for success, error, warning, and info messages.
Basic Notification
Play a sound when showing a toast notification:
import { useSound } from "@/hooks/use-sound"
import { notificationPop } from "@/sounds/notification-pop"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
export function BasicNotification() {
const [playNotification] = useSound(notificationPop)
const showNotification = () => {
playNotification()
toast("New message received!")
}
return (
<Button onClick={showNotification}>
Show Notification
</Button>
)
}
Success Notifications
Install the sound
npx soundcn@latest add success-chime
Import and use
import { useSound } from "@/hooks/use-sound"
import { successChime } from "@/sounds/success-chime"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
export function SuccessToast() {
const [playSuccess] = useSound(successChime)
const handleSuccess = () => {
playSuccess()
toast.success("Task completed successfully!")
}
return (
<Button onClick={handleSuccess}>
Complete Task
</Button>
)
}
Error Notifications
Use a distinct sound for errors to grab attention:
import { useSound } from "@/hooks/use-sound"
import { errorBuzz } from "@/sounds/error-buzz"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
export function ErrorToast() {
const [playError] = useSound(errorBuzz)
const handleError = () => {
playError()
toast.error("Failed to save changes")
}
return (
<Button variant="destructive" onClick={handleError}>
Trigger Error
</Button>
)
}
Complete Toast System
Create a unified toast system with different sounds:
import { useSound } from "@/hooks/use-sound"
import { successChime } from "@/sounds/success-chime"
import { errorBuzz } from "@/sounds/error-buzz"
import { notificationPop } from "@/sounds/notification-pop"
import { toast } from "sonner"
export function useToastSound() {
const [playSuccess] = useSound(successChime)
const [playError] = useSound(errorBuzz)
const [playInfo] = useSound(notificationPop, { volume: 0.6 })
return {
success: (message: string) => {
playSuccess()
toast.success(message)
},
error: (message: string) => {
playError()
toast.error(message)
},
info: (message: string) => {
playInfo()
toast(message)
},
}
}
Form Validation Feedback
Provide immediate audio feedback for form validation:
import { useState } from "react"
import { useSound } from "@/hooks/use-sound"
import { successChime } from "@/sounds/success-chime"
import { errorBuzz } from "@/sounds/error-buzz"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function FormWithSounds() {
const [email, setEmail] = useState("")
const [playSuccess] = useSound(successChime)
const [playError] = useSound(errorBuzz)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (email.includes("@")) {
playSuccess()
alert("Form submitted!")
} else {
playError()
alert("Invalid email")
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<Input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button type="submit">Submit</Button>
</form>
)
}
Real-time Notifications
Play sounds for real-time events like incoming messages:
import { useEffect } from "react"
import { useSound } from "@/hooks/use-sound"
import { notificationPop } from "@/sounds/notification-pop"
import { toast } from "sonner"
export function RealtimeNotifications() {
const [playNotification] = useSound(notificationPop, { volume: 0.7 })
useEffect(() => {
// Simulate WebSocket connection
const ws = new WebSocket("wss://example.com/notifications")
ws.onmessage = (event) => {
const notification = JSON.parse(event.data)
playNotification()
toast(notification.message)
}
return () => ws.close()
}, [])
return (
<div>
<p>Listening for notifications...</p>
</div>
)
}
For real-time notifications, consider using a lower volume (0.5-0.7) to avoid startling users, especially if notifications are frequent.
Priority Levels
Adjust volume and playback rate based on notification priority:
import { useSound } from "@/hooks/use-sound"
import { notificationPop } from "@/sounds/notification-pop"
import { errorBuzz } from "@/sounds/error-buzz"
import { toast } from "sonner"
type Priority = "low" | "medium" | "high" | "urgent"
export function usePriorityNotification() {
const [playPop] = useSound(notificationPop)
const [playUrgent] = useSound(errorBuzz)
const notify = (message: string, priority: Priority = "medium") => {
switch (priority) {
case "low":
playPop({ volume: 0.3 })
toast(message)
break
case "medium":
playPop({ volume: 0.6 })
toast(message)
break
case "high":
playPop({ volume: 0.9 })
toast(message, { duration: 5000 })
break
case "urgent":
playUrgent({ volume: 1.0 })
toast.error(message, { duration: 10000 })
break
}
}
return { notify }
}
Muting Controls
Let users control notification sounds:
import { createContext, useContext, useState, ReactNode } from "react"
import { useSound } from "@/hooks/use-sound"
import { notificationPop } from "@/sounds/notification-pop"
import { successChime } from "@/sounds/success-chime"
import { errorBuzz } from "@/sounds/error-buzz"
import { toast } from "sonner"
type SoundContextType = {
soundEnabled: boolean
toggleSound: () => void
playNotification: () => void
playSuccess: () => void
playError: () => void
}
const SoundContext = createContext<SoundContextType | undefined>(undefined)
export function SoundProvider({ children }: { children: ReactNode }) {
const [soundEnabled, setSoundEnabled] = useState(true)
const [playPop] = useSound(notificationPop, { soundEnabled })
const [playChime] = useSound(successChime, { soundEnabled })
const [playBuzz] = useSound(errorBuzz, { soundEnabled })
return (
<SoundContext.Provider value={{
soundEnabled,
toggleSound: () => setSoundEnabled(!soundEnabled),
playNotification: playPop,
playSuccess: playChime,
playError: playBuzz,
}}>
{children}
</SoundContext.Provider>
)
}
export function useSoundContext() {
const context = useContext(SoundContext)
if (!context) throw new Error("useSoundContext must be used within SoundProvider")
return context
}
Chaining Notifications
Create a sequence of sounds for multi-step processes:
import { useSound } from "@/hooks/use-sound"
import { notificationPop } from "@/sounds/notification-pop"
import { successChime } from "@/sounds/success-chime"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
export function ProcessWithSounds() {
const [playProgress] = useSound(notificationPop)
const [playSuccess] = useSound(successChime)
const startProcess = async () => {
toast("Starting upload...")
playProgress()
await new Promise(resolve => setTimeout(resolve, 1000))
toast("Processing...")
playProgress({ playbackRate: 1.2 })
await new Promise(resolve => setTimeout(resolve, 1000))
toast.success("Upload complete!")
playSuccess()
}
return (
<Button onClick={startProcess}>
Upload File
</Button>
)
}
Troubleshooting
Sounds play too frequently
Add debouncing to prevent sound spam:import { useCallback, useRef } from "react"
import { useSound } from "@/hooks/use-sound"
import { notificationPop } from "@/sounds/notification-pop"
export function DebouncedNotification() {
const [playSound] = useSound(notificationPop)
const lastPlayedRef = useRef(0)
const DEBOUNCE_MS = 1000
const playDebounced = useCallback(() => {
const now = Date.now()
if (now - lastPlayedRef.current > DEBOUNCE_MS) {
playSound()
lastPlayedRef.current = now
}
}, [playSound])
return playDebounced
}
Sounds don't match toast timing
Play the sound slightly before or with the toast:// Play sound first (recommended)
playSound()
toast("Message")
// Or use onPlay callback
const [play] = useSound(sound, {
onPlay: () => toast("Message")
})
Different sounds for dark/light mode?
You can conditionally use different sounds:import { useTheme } from "next-themes"
import { click8bit } from "@/sounds/click-8bit"
import { clickSoft } from "@/sounds/click-soft"
const { theme } = useTheme()
const sound = theme === "dark" ? click8bit : clickSoft
const [play] = useSound(sound)
Available Notification Sounds
notification-pop - Friendly, short pop (539ms)
success-chime - Positive confirmation (490ms)
error-buzz - Harsh error indicator (104ms)
Install notification sounds:
npx soundcn@latest add notification-pop success-chime error-buzz