
uisfx (UI SFX) is a dependency-free JavaScript library for adding sound feedback to your web apps.
It has semantic cues for interactions and application states, with interchangeable sound packs that retain the same cue names.
The browser runtime synthesizes sounds through Web Audio. The package also includes MP3 and Ogg assets for direct audio playback.
Features
- One-shot cues for selection, navigation, editing, feedback, rewards, and commerce.
- 6 controllable loops for ongoing application states.
- Per-cue volume, playback rate, cooldown, and retrigger settings.
- Master volume, mute control, and concurrent voice limits.
- HTML attributes for click, hover, press, and release sounds.
- Optional persistent preferences through localStorage or a storage adapter.
- Cancellable preloading for selected cues.
How To Use It
Installation
npm install uisfx
For a plain browser page, import the ES module from a CDN URL inside a <script type="module"> tag:
import { createUISFX } from 'https://cdn.jsdelivr.net/npm/uisfx/dist/index.js';
Basic Usage
Resume audio during the first user interaction before playing a cue.
<button id="play-sound" type="button">Play success sound</button>
import { createUISFX } from 'uisfx';
const ui = createUISFX({ pack: 'minimal' });
document.querySelector('#play-sound').addEventListener('click', async () => {
if (await ui.unlock()) ui.play('success');
});
Play And Stop State Loops
The library currently contains 6 loop cues (loading, processing, recording, connecting, scanning, and streaming). Hold the returned playback handle until the corresponding state ends.
<button id="start-preview" type="button">Start recording cue</button> <button id="stop-preview" type="button">Stop recording cue</button>
let recording;
document.querySelector('#start-preview').addEventListener('click', async () => {
if (!(await ui.unlock())) return;
recording?.stop();
recording = ui.play('recording');
});
document.querySelector('#stop-preview').addEventListener('click', () => {
recording?.stop();
recording = undefined;
ui.play('complete');
});
Change Sound Packs And Save Preferences
Enable preferences to save pack, volume, and enabled settings when their setters run.
const savedUi = createUISFX({
preferences: { key: 'my-app:audio' },
});
savedUi.setPack('studio');
savedUi.setVolume(0.5);
Bind Sounds With HTML Attributes
bindUISFX() delegates events from the document or a specified element. The optional player property connects the bindings to a player already created by your application.
<a href="/docs" data-uisfx-hover="hover">Documentation</a> <button type="button" data-uisfx-press="press" data-uisfx-release="release">Hold</button> <button type="button" data-uisfx="success" data-uisfx-pack="soft">Save changes</button>
import { bindUISFX } from 'uisfx';
const binding = bindUISFX(document, { player: ui });
// On view teardown:
// binding.unbind();
// Destroy ui separately when the application no longer needs it.
Preload And Adjust Playback
preload(cues?, options?) prepares all cues by default. Pass a cue array to select sounds and PreloadOptions.signal (AbortSignal) to cancel the remaining work.
const controller = new AbortController();
void ui.preload(['select', 'success', 'error'], {
signal: controller.signal,
});
ui.play('reaction', { retrigger: 'overlap' });
ui.play('hover', { cooldownMs: 0 });
Use Packaged Audio Files
The package includes sounds/{pack}/{cue}.mp3 and sounds/{pack}/{cue}.ogg. The uisfx/manifest export contains paths, byte sizes, durations, channel counts, loop flags, default volume, categories, and pack names.
This example uses Vite’s ?url asset import:
<button id="audio-preview" type="button">Preview audio file</button>
import soundUrl from 'uisfx/sounds/soft/success.mp3?url';
document.querySelector('#audio-preview').addEventListener('click', () => {
void new Audio(soundUrl).play();
});
Configuration Options
createUISFX(options?) takes a UISFXOptions object. bindUISFX(root?, options?) also accepts these options and adds player for reusing an existing instance.
pack(PackName, default'minimal'): Selects the initial sound pack. Saved preference is used when omitted.volume(number, default1): Master volume from0to1.enabled(boolean, defaulttrue): Enables or mutes playback.maxVoices(number, default8): Simultaneous voices, rounded and clamped from1to32. An older active voice is stopped when the limit is reached.cooldownMs(number, default cue-specific): Global override for cue throttling, clamped from0to10000milliseconds. Built-in values aretyping: 180,hover: 60,focus: 80,progress-step: 80, andvolume-change: 60ms; other cues use0.preferences(UISFXPreferences, default omitted): Enables saved pack, volume, and enabled settings.{}selects browserlocalStorage.context(AudioContext, default omitted): Uses an existing audio context.destroy()leaves caller-owned contexts open.
Preferences And Binding Options
preferences.key(string, default'uisfx:preferences'): Preference storage key.preferences.storage(UISFXPreferenceStorage, default browserlocalStorage): Adapter withgetItem(key): string | nullandsetItem(key, value): void.player(UISFXPlayer, default omitted): Existing player passed only tobindUISFX().
Playback Options
ui.play(cue, options?) accepts these PlayOptions fields:
volume(number, default cue’s volume): Sound-specific volume from0to1.loop(boolean, default cue’s loop setting): Overrides repeating playback.playbackRate(number, default1): Playback speed clamped from0.25to4.retrigger('restart' | 'overlap' | 'ignore'): Defaults to'restart'for one-shots and'ignore'for loops. Restarts, layers, or retains an existing playback of the cue.cooldownMs(number, default inherited from player or cue): Per-call cooldown from0to10000ms.
Public API Methods
const ui = createUISFX();
ui.unlock(); // Promise<boolean>; resume the AudioContext.
ui.play('success'); // PlayOptions optional; PlayingSFX | null.
ui.preload(['select']); // Promise<void>; optional cues and PreloadOptions.
ui.setPack('arcade'); // Select pack and move running loops.
ui.getPack(); // Current PackName.
ui.setVolume(0.5); // Set master volume and save if configured.
ui.getVolume(); // Current master volume.
ui.isEnabled(); // Current enabled state.
const playback = ui.play('processing');
playback?.stop(); // Stop one playback.
playback?.ended; // Promise<void> resolved when playback finishes.
ui.stopAll(); // Stop all active cues.
ui.setEnabled(false); // Mute and stop active sounds.
ui.destroy(); // Promise<void>; release owned audio resources.
Binding API
const binding = bindUISFX(document, { player: ui });
binding.player; // UISFXPlayer.
binding.unbind();
Catalog And Synthesis Exports
| Export | Description |
|---|---|
CATEGORIES | The 13 category definitions (id, label, description). |
CUES | Cue definitions and playback metadata. |
PACKS | Pack definitions and synthesis settings. |
cueNames | Array of the 78 cue IDs. |
packNames | Array of 12 pack IDs. |
getCue(name) | Returns CueDefinition for a CueName; throws for an unknown name. |
getPack(name) | Returns PackDefinition for a PackName; throws for an unknown name. |
getPlaybackMode(name) | Returns 'one-shot' or 'loop' for a CueName. |
soundUrl(pack, cue, format?) | Returns a package-relative sound filename. format is 'mp3' (default) or 'ogg'. |
createRecipe(pack, cue) | Creates a deterministic SoundRecipe for the pack and cue. |
renderRecipe(recipe, sampleRate?) | Returns stereo RenderedSound; default sampleRate is 44100. |
Exported TypeScript Types
| Type | Contract |
|---|---|
CueName | Union of the cue IDs in the complete cue list. |
PackName | Union of the 12 pack IDs. |
CategoryName | Union of the 13 category IDs. |
PlaybackMode | `’one-shot’ |
UISFXOptions | pack?, volume?, enabled?, maxVoices?, cooldownMs?, preferences?, context?. |
UISFXPreferences | key?, storage?. |
UISFXPreferenceStorage | getItem(key), setItem(key, value). |
PlayOptions | volume?, loop?, playbackRate?, retrigger?, cooldownMs?. |
PreloadOptions | signal?: AbortSignal. |
PlayingSFX | stop(): void, ended: Promise<void>. |
UISFXPlayer | All player methods in the reference above. |
BindOptions | UISFXOptions plus player?: UISFXPlayer. |
UISFXBinding | player: UISFXPlayer, unbind(): void. |
CueDefinition | name, label, category, description, duration, baseMidi, notes, noise?, transient?, panFrom?, panTo?, loop?, defaultVolume. |
PackDefinition | name, label, description, bestFor, color, waveform, pitch, duration, attack, decay, noise, transient, brightness, echo, bitDepth, harmonics, fmRatio?, fmDepth?, elasticity?, paper?, brush?, wood?, chime?. |
SoundRecipe | cue, pack, duration, notes, waveform, harmonics, attack, decay, noise, transient, brightness, echo, bitDepth, panFrom, panTo, loop, defaultVolume, fmRatio, fmDepth, elasticity, paper, brush, wood, chime. |
RenderedSound | sampleRate, duration, left: Float32Array, right: Float32Array, peak. |
HTML Data Attributes
| Attribute | Behavior |
|---|---|
data-uisfx="cue" | Play cue on click. |
data-uisfx-pack="pack" | Set player pack when clicked alongside data-uisfx. |
data-uisfx-hover="cue" | Play cue on pointer entry. |
data-uisfx-press="cue" | Play cue on pointer down. |
data-uisfx-release="cue" | Play cue on pointer up. |
Sound Packs
| Pack | Sound character |
|---|---|
minimal | Dry and restrained. |
soft | Warm and rounded. |
glass | Bright and crystalline. |
arcade | Chunky retro-game tones. |
mechanical | Switches and tactile clicks. |
organic | Wood, water, and natural textures. |
dreamy | Airy textures and soft sparkle. |
scifi | Digital pings and shimmer. |
rubber | Elastic taps and bounces. |
cinematic | Deep impacts and sustained tails. |
studio | Restrained sounds for editing controls. |
zen | Paper, wood, brush, and quiet chimes. |
Complete Semantic Cue List
| Category | Cue names |
|---|---|
| Input | hover, press, release, double-click, focus, long-press |
| Selection | select, deselect, toggle-on, toggle-off, check, uncheck |
| Navigation | open, close, back, forward, expand, collapse |
| Editing | delete, cancel, undo, redo, copy, paste |
| Movement | drag-start, drop, snap, swipe, reorder, invalid-drop |
| Communication | send, receive, notification, mention, typing, reaction |
| Feedback | success, error, warning, info, blocked, retry |
| Progress | start, stop, progress-step, complete, queued, checkpoint |
| Loops | loading, processing, recording, connecting, scanning, streaming |
| Media | play, pause, seek, volume-change, skip-next, skip-previous |
| System | connect, disconnect, lock, unlock, wake, sleep |
| Reward | reward, level-up, achievement, streak, badge, bonus |
| Commerce | add-to-cart, remove-from-cart, checkout, purchase, coupon, refund |
Alternatives
- Add UI Sound Effects to Web Apps With Tiks and Web Audio API
- Add Web Audio Feedback to Bootstrap 5 Components – Bootstrap UI Sounds
FAQs
Q: Why is there no sound on the first interaction?
A: Browsers can suspend AudioContext until a user gesture. Call await ui.unlock() in the first pointer or keyboard handler.
Q: Will HTML bindings work for elements inserted later?
A: Yes. bindUISFX() delegates its event listeners from the configured root.
Q: Can I use uisfx with server-side rendering?
A: Initialize playback on the client. bindUISFX() requires a browser document or element, and sound playback uses AudioContext.







