BloxwapSFX
Guides

Sound design

Which sound for which moment, and how much sound is too much.

Interface sounds work when they confirm something the user did, or tell them something they would otherwise miss. They fail when every surface makes noise. A few principles:

  1. Sound the verbs, not the nouns. A press, a toggle, a completed payment. Not "a card exists".
  2. Quieter than you think. Start with setVolume(0.5) and go down, not up.
  3. Short beats long. Controls should use the sub-50 ms sounds (tick, press, release, toggle). Save the longer sounds for outcomes.
  4. Match the valence. Rising sounds (success, ready, chime) feel positive and falling ones (error, droplet, loss) feel negative. Don't play a rising sound for a failure.
  5. Always offer a mute. Some people can't or don't want to hear your interface. See Accessibility.

A map of moments

MomentSound
Hover over navigation or
Hover over a primary call to action
Press and release a button +
Flip a switch, change tabs
Next page or slide
Open or expand
Close or dismiss
Start an async action
Data arrived
Action succeeded
Action failed (recoverable)
New message or alert
Select or pick up an item
Funds received
Reward, win, or payout
Loss or liquidation
Delight moments (use sparingly)

Variation without new sounds

play() accepts a playback rate and a stereo pan. Small changes keep a repeated sound from feeling mechanical, and they add almost no cost:

// A stepper that rises in pitch as the value grows.
play('tick', { rate: 0.9 + value / 50 });

// A sound that comes from the side of the screen where it happened.
play('notification', { pan: toast.side === 'left' ? -0.6 : 0.6 });

Combine sounds with delay

delay starts a sound a number of seconds from now. It is scheduled on the audio clock, so the gap stays exact even when the main thread is busy. That lets you build a compound sound from two short ones.

A tap is a press followed by a release. bind() plays them on pointer down and up, but some actions fire only once, such as a keyboard shortcut or a gesture recognizer's tap. Play both from one handler:

play('press');
play('release', { delay: 0.09 });

90 ms is roughly how long a quick tap holds the button down. The delayed sound counts as playing from the moment you call play(), so stopAll() cancels it too. delay is clamped to 0–10 seconds.

Native apps can bake the same tap into one file with sfx-wav --combo.

Edit on GitHub

On this page