Sounds
Snappy, thocky interface sounds synthesised in the browser, in eight families. No audio files, off until you turn them on.
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.
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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | true | Wire it to the user's setting. false silences every sound below. |
| volume | number | 0.6 | Master level from 0 to 1, before the built-in limiter. |
| library | Record<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.
| Prop | Type | Default | Description |
|---|---|---|---|
| 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.
| Prop | Type | Default | Description |
|---|---|---|---|
| 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.
| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |