GitHub
Open Github

UseSound

Play customizable sound effects with optional haptic feedback, built on the Web Audio API.

Installation

pnpm add @hookraft/use-sound

How it works

useSound synthesizes UI sounds entirely in the browser using the Web Audio API — no audio files, no network requests, no external dependencies. Every sound is procedurally generated from oscillators, filters, and gain envelopes tuned to feel natural for UI interactions.

Haptics are layered on top via navigator.vibrate() using pre-mapped patterns that match each sound's intent. Both sound and haptics respect prefers-reduced-motion automatically.





Toast / notification

Fire "notification" when a toast appears and "success" or "error" based on its type. Gives each toast distinct audible identity.


Loading state

"loading" loops automatically until you call stop("loading"). Use isPlaying("loading") to track its state and swap your UI accordingly.


Scroll ticking

Throttle "scroll" so it fires at most once every 80ms during list scroll — any faster and it becomes noise rather than feedback.


Types

type SoundName =
  | "click" | "hover" | "scroll"
  | "modal-open" | "modal-close"
  | "success" | "error" | "warning"
  | "toggle-on" | "toggle-off"
  | "notification" | "delete"
  | "expand" | "collapse"
  | "swipe" | "pop"
  | "minimize" | "maximize"
  | "typing" | "loading" | "complete" | "coin"

type Theme =
  | "soft"
  | "mechanical"
  | "digital"
  | "wooden"
  | "glass"

type HapticPattern =
  | "click" | "double"
  | "success" | "error" | "warning"
  | "notification"
  | "impact-light" | "impact-medium" | "impact-heavy"
  | "selection" | "long"

interface Options {
  volume?:        number
  pitch?:         "low" | "mid" | "high" | number
  speed?:         "slow" | "normal" | "fast"
  reverb?:        boolean
  stereo?:        number
  haptic?:        boolean
  hapticPattern?: HapticPattern
}

interface GlobalOptions {
  globalVolume?: number
  muted?:        boolean
  theme?:        Theme
  haptics?:      boolean
}

interface Return {
  play:           (name: SoundName, options?: Options) => void
  stop:           (name?: SoundName) => void
  mute:           () => void
  unmute:         () => void
  isMuted:        boolean
  isPlaying:      (name: SoundName) => boolean
  triggerHaptic:  (pattern: HapticPattern) => void
}

useSound

import { useSound } from "@hookraft/use-sound"
const { play, stop, mute, unmute, isMuted, isPlaying, triggerHaptic } = useSound({
  theme:        "soft",
  globalVolume: 0.3,
  haptics:      true,
})

GlobalOptions

PropTypeDefault
theme
useSound.Theme
"soft"
globalVolume
number
0.3
muted
boolean
false
haptics
boolean
false

Return value

PropTypeDefault
play
(name: SoundName, options?: Options) => void
-
stop
(name?: SoundName) => void
-
mute
() => void
-
unmute
() => void
-
isMuted
boolean
-
isPlaying
(name: SoundName) => boolean
-
triggerHaptic
(pattern: HapticPattern) => void
-

Per-call Options

PropTypeDefault
volume
number
-
pitch
"low" | "mid" | "high" | number
-
speed
"slow" | "normal" | "fast"
-
reverb
boolean
-
stereo
number
-
haptic
boolean
-
hapticPattern
useSound.HapticPattern
-

Sound palette

Every sound is tuned for a specific UI context. Each maps automatically to a haptic pattern when haptics are enabled.

NameContextHaptic
"click"Button pressclick
"hover"Icon / link mouseenterimpact-light
"scroll"List scroll tickselection
"modal-open"Modal / drawer openimpact-medium
"modal-close"Modal / drawer closeimpact-light
"success"Form submit, save confirmedsuccess
"error"Validation failureerror
"warning"Alert, caution statewarning
"toggle-on"Switch / checkbox ondouble
"toggle-off"Switch / checkbox offdouble
"notification"Toast / badge appearnotification
"delete"Destructive actionimpact-heavy
"expand"Accordion / dropdown openimpact-light
"collapse"Accordion / dropdown closeimpact-light
"swipe"Card swipe / drawer dragimpact-medium
"pop"Tooltip / popover appearclick
"minimize"Panel minimizeimpact-light
"maximize"Panel maximizeimpact-medium
"typing"Each keystrokeselection
"loading"Spinner / skeleton start (loops)selection
"complete"Loading donesuccess
"coin"Points / reward earneddouble

Themes

Five themes ship out of the box. Each changes the oscillator waveform, filter shape, and envelope timing — making the same sound feel mechanical, glassy, warm, or digital depending on your product's personality.

ThemeCharacterOscillator
"soft"Muted, gentle — iOS-like (default)Sine
"mechanical"Keyboard / typewriter clicksSquare
"digital"Crisp, synthetic, sci-fiSawtooth
"wooden"Warm, organic tapsTriangle
"glass"Bright, crystalline pingsSine + peaking filter


Haptic patterns

Haptic patterns use navigator.vibrate() with tuned duration arrays. They fire automatically alongside sounds when haptics: true, or you can trigger them manually via triggerHaptic().

Haptics are silently skipped when:

  • navigator.vibrate is unavailable (desktop browsers, iOS Safari)
  • prefers-reduced-motion: reduce is set
PatternVibration sequence
"click"10ms
"double"10 · 50 · 10ms
"success"15 · 40 · 40ms
"error"30 · 20 · 30 · 20 · 30ms
"warning"20 · 60 · 20ms
"notification"25ms
"impact-light"5ms
"impact-medium"15ms
"impact-heavy"30ms
"selection"8ms
"long"10 · 30 · 10 · 30 · 40ms