UI Sound Kit
Lightweight interface sounds — click, hover, success, error and more — synthesized live with Web Audio, with built-in volume control, a global switch, and preference-respecting behavior; no audio files, no extra requests.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add ui-sound-kit.enUI Sound Kit is a React UI sound effects kit that synthesizes click, notification, success, and error sounds on the fly with the browser's Web Audio API. It exposes a useSound() hook through React Context, and its bundled buttons are styled with Tailwind CSS.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<SoundButton/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Feedback sounds for key actions such as form submission, completed payments, or failed uploads, so the result is easier to notice.
- Notification sounds when a new message arrives in chat, support, or collaboration tools.
- Button sounds in gamified interfaces, interactive onboarding, or educational products for kids.
- A "UI sounds" toggle and volume control on a settings page, with the preference saved in the browser automatically.
- Not a good fit: playing your own audio files or background music. The kit ships six fixed synthesized sounds and has no API for loading audio files.
Installation
npx webberui@latest add https://webberui.com/r/ui-sound-kit.en.jsonOr, once registries are configured in components.json, install it as @webberui/ui-sound-kit.
Usage
Wrap SoundProvider once around the scope that needs sound, and any component inside it can get play() plus the switch/volume controls through useSound():
import {
SoundProvider,
useSound,
} from "@/components/ui/ui-sound-kit";
function App() {
return (
<SoundProvider defaultVolume={0.5}>
<SaveButton />
</SoundProvider>
);
}
function SaveButton() {
const { play } = useSound();
return (
<button
onClick={async () => {
try {
await save();
play("success");
} catch {
play("error");
}
}}
>
Save
</button>
);
}Built-in sound names: "click", "hover", "success", "error", "toggle", "notification" (the full list is available from SOUND_NAMES).
Built-in components
SoundButton is a button that makes sound, playing automatically on click (and optionally on hover); SoundToggle is the global switch button:
import {
SoundButton,
SoundToggle,
} from "@/components/ui/ui-sound-kit";
<SoundButton sound="click" hoverSound="hover">
Play
</SoundButton>
<SoundToggle />Examples
Mount the provider in the Next.js root layout
SoundProvider is a client component, so you can place it directly in a Server Component app/layout.tsx and wrap children.
import { SoundProvider } from "@/components/ui/ui-sound-kit";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SoundProvider defaultVolume={0.4}>{children}</SoundProvider>
</body>
</html>
);
}Toggle and volume slider on a settings page
Connect a native range input to volume and setVolume from useSound(), and let SoundToggle handle the global on/off switch.
"use client";
import { SoundToggle, useSound } from "@/components/ui/ui-sound-kit";
export function SoundSettings() {
const { volume, setVolume, enabled } = useSound();
return (
<div className="flex items-center gap-3">
<SoundToggle />
<input
type="range"
min={0}
max={1}
step={0.05}
value={volume}
disabled={!enabled}
onChange={(e) => setVolume(Number(e.target.value))}
aria-label="UI sound volume"
/>
</div>
);
}Play a notification sound on new messages
The second argument to play() sets a volume multiplier for that one playback, applied on top of the global volume.
"use client";
import * as React from "react";
import { useSound } from "@/components/ui/ui-sound-kit";
export function useMessageSound(socket: WebSocket) {
const { play } = useSound();
React.useEffect(() => {
const onMessage = () => play("notification", { volume: 0.6 });
socket.addEventListener("message", onMessage);
return () => socket.removeEventListener("message", onMessage);
}, [socket, play]);
}Props
SoundProvider
| Prop | Type | Default | Description |
|---|---|---|---|
defaultEnabled | boolean | true | Whether sound starts enabled (overridden after mount when a persisted preference exists) |
defaultVolume | number | 0.5 | Initial global volume, 0 to 1 |
storageKey | string | null | "wb-sound-kit" | localStorage key that persists the switch and volume; null disables persistence |
respectReducedMotion | boolean | true | Whether to also mute when the system has "reduce motion" enabled |
What useSound() returns
| Name | Type | Description |
|---|---|---|
play | (name, options?) => void | Synthesizes and plays a built-in sound; options.volume is a one-off volume multiplier |
enabled | boolean | Whether global sound is on |
setEnabled | (enabled: boolean) => void | Turn global sound on/off |
volume | number | Global volume, 0 to 1 |
setVolume | (volume: number) => void | Set the global volume (automatically clamped to 0–1) |
SoundButton
| Prop | Type | Default | Description |
|---|---|---|---|
sound | SoundName | "click" | The sound played on click |
hoverSound | SoundName | — | When set, this sound also plays as the cursor moves in |
All other native button attributes are forwarded. SoundToggle forwards native button attributes too, and uses chime (default true) to control whether a confirmation sound plays when it is switched on.
How it works
- Synthesized live, zero assets: sounds are synthesized on the fly with Web Audio's
OscillatorNode+GainNode— no audio file is loaded and no extra request is made. - Lazy initialization: the
AudioContextis created and auto-resumed only on the firstplay()(usually from a user click gesture), which complies with browser autoplay policies; it also tries to recover if it wassuspended while the tab was in the background. - A single audio graph: the whole Provider shares one
AudioContextand one master gain node; every playback attaches its own bus gain, applies the one-off volume multiplier, and is then recycled. - Persisted preferences: the switch and volume are written to
localStorageby default and carried across refreshes; turn this off withstorageKey={null}.
Accessibility
SoundToggleexpresses its state witharia-pressed, and switches itsaria-label(enable sound/disable sound) and icon along with the state.- The volume slider is a native
<input type="range">with anaria-label, operable by keyboard. - When the user has "reduce motion" enabled at the system level,
respectReducedMotionmutes by default, respecting the preference for less stimulation; with sound off, interaction behavior (onClickand so on) is unaffected — it simply makes no sound.
FAQ
Does it work with the Next.js App Router?
Yes. The source already has "use client", and SoundProvider can sit directly inside a Server Component layout. useSound(), SoundButton, and SoundToggle must all be used inside a SoundProvider; otherwise useSound() throws an error.
Why is there no sound the first time after the page loads?
Browser autoplay policies require audio to start from a user gesture. The AudioContext is only created, and resumed if needed, on the first play() call, so call it from an event such as a click rather than automatically on page load.
Do I need audio files?
No. Every sound is synthesized in real time with OscillatorNode and GainNode, with no audio files and no extra requests. The only dependencies are motion (to detect reduced motion) and lucide-react (toggle icons).
Is the user's preference remembered?
Yes. The on/off state and volume are saved to localStorage under the "wb-sound-kit" key by default; change the key with storageKey, or pass null to disable saving.
Does it play sounds when reduced motion is on?
Not by default. respectReducedMotion defaults to true, so play() stays silent when the system's reduced-motion setting is on, while handlers such as onClick still run. Pass respectReducedMotion={false} to turn this off.
Broadcast Bar System
A top broadcast bar system that stacks multiple announcements, rotates them through a priority queue, supports countdowns, CTAs, and dismissal memory, and springs its height changes so the content below is pushed smoothly.
Live Glance Strip
A live-activity information strip — the iOS Live Activity vocabulary brought to the web as a frosted-glass pill, with status text flipping vertically on update, a progress track advancing live, and details expanding on click.