WebberUI

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.

Loading preview…
npx shadcn@latest add https://webberui.com/r/ui-sound-kit.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

0.5
<UiSoundKit />

Installation

npx shadcn@latest add https://webberui.com/r/ui-sound-kit.json

Or, 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 />

Props

SoundProvider

PropTypeDefaultDescription
defaultEnabledbooleantrueWhether sound starts enabled (overridden after mount when a persisted preference exists)
defaultVolumenumber0.5Initial global volume, 0 to 1
storageKeystring | null"wb-sound-kit"localStorage key that persists the switch and volume; null disables persistence
respectReducedMotionbooleantrueWhether to also mute when the system has "reduce motion" enabled

What useSound() returns

NameTypeDescription
play(name, options?) => voidSynthesizes and plays a built-in sound; options.volume is a one-off volume multiplier
enabledbooleanWhether global sound is on
setEnabled(enabled: boolean) => voidTurn global sound on/off
volumenumberGlobal volume, 0 to 1
setVolume(volume: number) => voidSet the global volume (automatically clamped to 0–1)

SoundButton

PropTypeDefaultDescription
soundSoundName"click"The sound played on click
hoverSoundSoundNameWhen 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 AudioContext is created and auto-resumed only on the first play() (usually from a user click gesture), which complies with browser autoplay policies; it also tries to recover if it was suspended while the tab was in the background.
  • A single audio graph: the whole Provider shares one AudioContext and 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 localStorage by default and carried across refreshes; turn this off with storageKey={null}.

Accessibility

  • SoundToggle expresses its state with aria-pressed, and switches its aria-label (enable sound/disable sound) and icon along with the state.
  • The volume slider is a native <input type="range"> with an aria-label, operable by keyboard.
  • When the user has "reduce motion" enabled at the system level, respectReducedMotion mutes by default, respecting the preference for less stimulation; with sound off, interaction behavior (onClick and so on) is unaffected — it simply makes no sound.

On this page