Sounds

Snappy, thocky interface sounds synthesised in the browser, in eight families. No audio files, off until you turn them on.

Tap
tap
Pop
pop
Copy
copy
SendPro
send
DeletePro
delete
Success
success
Error
error
Warning
warning
CompletePro
complete
LikePro
like
Forward
forward
Back
back
TabPro
tab
SwipePro
swipe
Notify
notify
MessagePro
message
BubblePro
bubble
MentionPro
mention
AlertPro
alert
Toggle On
toggleOn
Toggle Off
toggleOff
SelectPro
select
DeselectPro
deselect
Pick Up
pickUp
Drop
drop
ExpandPro
expand
CollapsePro
collapse
Pop InPro
popIn
Pop OutPro
popOut
StepPro
step
ChargePro
charge
FinishPro
finish
StallPro
stall
StartupPro
startup
ShutdownPro
shutdown
LockPro
lock
UnlockPro
unlock

When a sound earns its place#

One click, one hit: a sound confirms something the eye may have missed, a message sent, a payment through, a file dropped in place. Give one to moments that finish something, never to hovering, scrolling or typing. Keep them quiet and short, and let the user turn them off.

They are built to be felt more than heard: a crisp click on top, a short woody body underneath, gone in under a tenth of a second. Every sound is a few oscillators and filtered noise, drawn in about a kilobyte of code. Nothing loads until the first one plays, and the audio engine waits for the user's first click as browsers require. Thirteen sounds ship free in @/lib/sound; the other twenty-four live in the Pro soundLibrary.

Usage#

Wrap the app in SoundProvider and wire enabled to a user setting. Components call useSound from their handlers; outside a provider it stays silent, so a component can carry sounds without forcing them on anyone.

The Pro library#

Pass soundLibrary to the provider and its sounds are called by name like the free ones.

providers.tsxTSX
import { SoundProvider } from "@/lib/sound"import { soundLibrary } from "@/components/ui/sound-library"<SoundProvider library={soundLibrary}>{children}</SoundProvider>const send = useSound("send")

Your own sounds#

A sound is a recipe of voices on a timeline. Start from one in the grid and change a pitch, a decay or a filter until it fits your product.

sounds.tsTSX
import { defineSound } from "@/lib/sound"export const stamp = defineSound("actions", (s) => {  s.thock({ pitch: 150, bright: 1800, length: 1.2 })  s.thock({ pitch: 300, bright: 3600, at: 0.04, gain: 0.5 })})<SoundProvider library={{ stamp }}>{children}</SoundProvider>

API reference#

SoundProvider

Turns sounds on for everything inside it. Without one, useSound stays silent. From @/lib/sound.

PropTypeDefaultDescription
enabledbooleantrueWire it to the user's setting. false silences every sound below.
volumenumber0.6Master level from 0 to 1, before the built-in limiter.
libraryRecord<string, SoundRecipe>-More sounds to call by name: the Pro soundLibrary, or your own defineSound recipes.

useSound

Returns a stable function that plays one sound. Call it from the handler of the change it belongs to.

PropTypeDefaultDescription
sound*SoundName | string | SoundRecipe-A name from sounds or the provider's library, or a recipe.

defineSound

Builds a recipe from a family and a play function. The synth has three voices: thock, the keycap hit most Koala sounds are built on, plus tone and noise.

PropTypeDefaultDescription
family*SoundFamily-actions, feedback, navigation, notifications, selection, movement, progress or system.
thock(options?){ pitch?, bright?, at?, gain?, length?, pan? }-A short percussive hit: a click on top, a body that drops into pitch underneath. bright sets the click, length stretches the body.
tone(options){ freq, to?, wave?, at?, attack?, decay, gain?, detune?, pan? }-An oscillator. to glides the pitch over the note, at delays it in seconds.
noise(options){ filter?, freq, to?, q?, at?, attack?, decay, gain?, pan? }-Filtered noise for clicks, air and texture. to sweeps the filter.

playSound / renderSound / toWav

The engine without React: play a recipe now, render it offline to an AudioBuffer, or turn that buffer into a WAV Blob.

PropTypeDefaultDescription
playSound(recipe, volume?)void-The same sound fired twice within 40ms plays once.
renderSound(recipe, seconds?, volume?)Promise<AudioBuffer>-48kHz mono, rendered through the same limiter you hear.
toWav(buffer)Blob-16-bit PCM with the silent tail trimmed.