1. Installation
The library is zero-dependency and uses the native Web Audio API to synthesize all sounds procedurally. There are no MP3/WAV files to load.
npm install @thenormvg/web-have-sounds
AI Agent Skill
If you are using Claude Code or an agentic assistant, install the web-have-sounds skill to teach your AI how to perfectly implement this library according to UI audio best practices.
npx skills add normvg/web-have-sound --skill web-have-sounds
2. Quick Start
You can trigger sounds manually via code, or declaratively using HTML data attributes.
import { configureUISounds, playUISound } from '@thenormvg/web-have-sounds'; // Initialize the global engine configureUISounds({ feel: 'aero', // Try 'arcade', 'industrial', 'glass'... volume: 0.8, }); // Trigger a sound playUISound('click'); playUISound('error', 'industrial'); // Override the global feel for a specific event
3. Architecture
Built-ins are pre-registered the same way as custom entries — one single code path.
configure / registerFeel / registerSound / registerLoop
↓
Catalog (names)
┌──────────┴──────────┐
play() LoopRuntime
(one-shots) (long-running)
└──────────┬──────────┘
↓
AudioEngine
synth → [pan] → [loop bus] → master → out4. Global Config
Configure the global state of the singleton audio engine.
configureUISounds({ feel: 'industrial', volume: 0.7, enabled: true, randomize: true, throttleMs: { hover: 150, keystroke: 80 }, debug: true, // console.warn for typos & bad setup }); setUISoundsEnabled(false); setMasterVolume(0.5);
5. Feels Catalog
"Feels" are synthesizer presets that completely change the aesthetic of all sounds. They dictate the ADSR envelope multipliers, oscillator waveforms, and low-pass filter settings.
Freq: 2000Hz
Q: 1
Freq: 3500Hz
Q: 2
Freq: 4000Hz
Q: 8
Freq: 2500Hz
Q: 3
Freq: 6000Hz
Q: 10
Freq: 3000Hz
Q: 12
Freq: 2000Hz
Q: 1
Freq: 1500Hz
Q: 2
Freq: 5500Hz
Q: 4
6. One-Shot Sounds
Click any of the buttons below to preview the built-in one-shot sounds. The sounds will synthesize according to the "Global Feel" selected at the top of this page.
Interactive Sandbox
Change the global feel to preview how the library renders audio below.
7. Ambient Loops
Ambient loops are long-running procedural soundscapes. They automatically fade in and out. You can run multiple loops simultaneously.
Custom loops
You can register custom procedural loops that run indefinitely and can be faded in/out.
registerLoop( 'upload', ({ ctx, time, params, volume, connect }) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); const lfo = ctx.createOscillator(); const lfoGain = ctx.createGain(); osc.type = 'sine'; osc.frequency.value = 330 * params.pitchMult; gain.gain.value = 0.06 * volume; lfo.frequency.value = 3; lfoGain.gain.value = 0.03 * volume; lfo.connect(lfoGain); lfoGain.connect(gain.gain); osc.connect(gain); connect(gain); osc.start(time); lfo.start(time); // Return sources so stopLoop can end them after fade-out return { sources: [osc, lfo] }; }, { fadeIn: 0.1, fadeOut: 0.25 } ); startLoop('upload'); stopLoop('upload');
8. Framework Integration
The easiest way to use the library is with declarative HTML attributes. Run bindUISounds() when your app mounts.
// HTML syntax <button data-uisound="click" data-uisound-hover="hover"> Submit </button>
Vue / Nuxt
import { onMounted, onUnmounted } from 'vue'; import { bindUISounds } from '@thenormvg/web-have-sounds'; export default { setup() { onMounted(() => { const unbind = bindUISounds(); onUnmounted(unbind); }); } }
React / Next.js
import { useEffect } from 'react'; import { bindUISounds } from '@thenormvg/web-have-sounds'; function App() { useEffect(() => { return bindUISounds(); }, []); return <div>...</div>; }
9. Custom API
For advanced users, you can write your own Web Audio API nodes and register them as sounds or feels into the catalog.
import { registerSound, playUISound } from '@thenormvg/web-have-sounds'; registerSound('my_laser', ({ ctx, time, params, volume, connect }) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); // Use the global feel params osc.type = params.oscType; osc.frequency.setValueAtTime(800 * params.pitchMult, time); osc.frequency.exponentialRampToValueAtTime(100, time + 0.2); gain.gain.setValueAtTime(volume, time); gain.gain.exponentialRampToValueAtTime(0.01, time + 0.2); osc.connect(gain); // Must connect to the provided connect() callback to route // through the master bus and volume controls connect(gain); osc.start(time); osc.stop(time + 0.2); }); playUISound('my_laser');
10. Isolated Engine (Optional)
The default exported functions operate on a shared global singleton, which is perfect for most web apps. If you need a completely isolated audio context (e.g. for independent game audio, or if you have multiple isolated micro-frontends on one page), you can instantiate a separate engine.
import { createUISounds } from '@thenormvg/web-have-sounds'; const sfx = createUISounds(); sfx.configure({ feel: 'arcade', debug: true }); sfx.play('click'); sfx.bind({ root: document.getElementById('game') });
11. API Cheat Sheet
| API | Role |
|---|---|
playUISound / configureUISounds | Core interaction |
warmUpAudio / getAudioContext | Audio unlock |
setUISoundsEnabled / setMasterVolume | Mute & level |
registerFeel / registerSound | Catalog |
startLoop / stopLoop / stopAllLoops | Long-running beds |
bindUISounds | DOM attributes |
createUISounds | Isolated instance |
12. Error Handling Philosophy
This library is designed for UI augmentation and strictly prioritizes application stability. It follows a "graceful degradation" model.
- Silent Failures: The library uses extensive
try/catchblocks internally. If the browser blocks audio (e.g., due to autoplay policies), or if a sound fails to synthesize, the error is caught and swallowed. This guarantees that callingplayUISound()will never throw an exception that could crash your React/Vue application. - Opt-in Debugging: If you need to troubleshoot why sounds aren't playing (e.g., missing names, bad registrations, or autoplay blocks), initialize with debug mode. This routes internal failures to
console.warnso you can see them during development without crashing the app.
import { configureUISounds } from '@thenormvg/web-have-sounds'; // Initialize with debug mode to route non-fatal // audio exceptions to console.warn configureUISounds({ debug: true });