Foundations

Sound

A game answers every press. Twelve short sounds — one for each kind of moment — that every component already makes, quietly, through one provider the app mounts at its root.

The twelve

Press each to hear it. The speaker in the toolbar turns the gallery’s sounds off. Soft clicks for the cursor, a sci-fi select and back, short whooshes for tabs and overlays — cut to their body and levelled for their role, 61 KB together.

navigate

The cursor lands on something: a row by key or by mouse, a button under the pointer, a dropdown item.

MenuListButtonTileSelectDropdownMenu

adjust

A value steps. A drag ticks at most every 70ms.

OptionStepperMenuSliderColorGridMenuChoice

tab

A section switches, by Q / E, a click or the arrows.

MenuTabsTabs

select

A press or a confirm. A purchase is still only this — the outcome comes later.

ButtonMenuItemTileCheckoutBarSelectDropdownMenu

back

A step back out of a sub-menu: BACKSPACE or the back row.

MenuList

toggle-on

Something switched on.

SwitchCheckboxMenuToggleDropdownMenuCheckboxItem

toggle-off

Something switched off.

SwitchCheckboxMenuToggle

open

An overlay or a menu arrives.

MenuScreenDialogSheetSelectDropdownMenuFaqAccordion

close

It leaves — unless a press just closed it, which already made its sound.

MenuScreenDialogSheetSelectDropdownMenuFaqAccordion

success

The server said yes: bought, saved, sent. The app plays it, or a success toast does.

Toasterthe app

error

The server said no, or a stepper hit its end.

ToasterOptionStepperthe app

notify

Something arrived unasked: any other toast.

Toaster

Rules

  1. 1One action, one sound. The provider drops a sound that lands within 140ms of a weightier one: the select of a dropdown item swallows the close after it, the open of a dialog swallows the navigate of its first item. Never play two on purpose.
  2. 2Only actions sound. Nothing plays on its own: no ambience, no loop, no sound on page load, no sound for an animation. A toast is the one exception — it arrives unasked, so it announces itself.
  3. 3No typing sounds, no scroll sounds, no hover sounds on text or on a swatch grid. The pointer ticks only on things that act.
  4. 4A press is select, whatever it buys. success and error are the server’s answer: play them, or toast them, when the answer comes back — never optimistically on the click.
  5. 5Use the components and they sound by themselves. A hand-made control takes data-sfx="select" and data-sfx-hover="navigate", or calls useUiSound() — never an <audio> of its own.
  6. 6Frequent sounds sit lowest. navigate is the quietest of the twelve and success the loudest; a cursor walking a list must never be louder than a purchase. The default volume is 0.35: felt more than heard, under the game’s own audio.
  7. 7Players can turn it off. The game ties the player’s volume to its settings; a site that should not tick mutes navigate (volumes: { navigate: 0 }).

Setting it up

Once, at the root. Without a provider every component is silent, so nothing sounds by accident in an app that has not opted in.
import { createUiSoundPlayer, UiSoundsProvider } from '@/components/ds/ui-sounds';

const player = createUiSoundPlayer({ volume: 0.35 });

export function Root({ children }) {
  return <UiSoundsProvider onSound={player.play}>{children}</UiSoundsProvider>;
}

In the game

Pass the webview’s shared context — createUiSoundPlayer({ context: getSharedAudioContext }) — so the sounds go through the one AudioContext the NUI already owns, and set the volume from the player’s settings. The sounds play inside the NUI, so they are the same in the hub and in the city.

Credits

Two of the authors ask to be credited. An app that ships these sounds carries the credit in its credits screen or notices.