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 → out

4. 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.

soft
Osc: sine
Freq: 2000Hz
Q: 1
aero
Osc: sine
Freq: 3500Hz
Q: 2
arcade
Osc: square
Freq: 4000Hz
Q: 8
organic
Osc: triangle
Freq: 2500Hz
Q: 3
glass
Osc: sine
Freq: 6000Hz
Q: 10
industrial
Osc: sawtooth
Freq: 3000Hz
Q: 12
minimal
Osc: sine
Freq: 2000Hz
Q: 1
retro
Osc: square
Freq: 1500Hz
Q: 2
crisp
Osc: triangle
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.

Sound paletteoscilloscope output
chime  1047 Hz · 0.36 s

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

APIRole
playUISound / configureUISoundsCore interaction
warmUpAudio / getAudioContextAudio unlock
setUISoundsEnabled / setMasterVolumeMute & level
registerFeel / registerSoundCatalog
startLoop / stopLoop / stopAllLoopsLong-running beds
bindUISoundsDOM attributes
createUISoundsIsolated 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/catch blocks 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 calling playUISound() 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.warn so 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 
});