Synthesizing UI Sounds with the Web Audio API
A few oscillators and about forty lines of code, with no audio files at all.
If you opt in, this site makes little sounds when you hover the nav or click a card. None of them are audio files. They're synthesized in the browser with the Web Audio API, which means they add nothing to the bundle.
A sound is just an envelope on an oscillator
Each effect is a tiny voice: a waveform, a frequency, and a gain envelope short enough that it reads as a tick rather than a tone.
const osc = ctx.createOscillator();
const amp = ctx.createGain();
osc.type = "triangle";
osc.frequency.setValueAtTime(440, start);
osc.frequency.exponentialRampToValueAtTime(330, end); // satisfying pitch drop
amp.gain.setValueAtTime(0.0001, start);
amp.gain.exponentialRampToValueAtTime(0.08, start + 0.01); // quick attack
amp.gain.exponentialRampToValueAtTime(0.0001, end); // smooth decay
The exponential ramps matter. Without them you get a click at the start and end of every note as the gain snaps between values.
Defaults are a design decision
Three rules keep it from being annoying:
- Off by default. Sound is opt-in via a toggle in the nav.
- Remembered. The preference lives in
localStorage. - Reduced motion wins. If the visitor has
prefers-reduced-motionset, audio is disabled outright and the toggle disappears.
A detail like this shouldn't cost anything for people who didn't ask for it. Synthesizing the sounds keeps the bundle small, and the defaults keep them out of the way.