Cuelume
#Cuelume
Seventeen carefully designed interaction sounds for the web. Synthesized live with Web Audio, with no audio files and zero runtime dependencies.
Cuelume is a curated sound palette, not an audio engine. It gives buttons, links, toggles, and completed actions clear feedback without asking developers to design sounds themselves. Add an attribute, call bind(), done.
#Install
npm install cuelume
#Requirements
Cuelume is ESM-only. Use it through native import or an ESM-compatible bundler; CommonJS require() is not supported.
It targets modern browsers with ES modules and the Web Audio API. Server-side imports are safe, but sound playback only runs in the browser.
#Quick start
Add data attributes to your markup:
<button data-cuelume-press data-cuelume-release>Save</button> <a data-cuelume-hover="tick">Docs</a> <button data-cuelume-toggle>Dark mode</button> <button data-cuelume-press="pulse" data-cuelume-release="scan">Launch</button>
Then wire everything up once:
import { bind } from "cuelume"; bind();
| Attribute | Fires on | Default sound |
|---|---|---|
data-cuelume-hover |
pointerenter |
chime |
data-cuelume-press |
pointerdown |
press |
data-cuelume-release |
pointerup |
release |
data-cuelume-toggle |
click |
toggle |
Leave the attribute value empty to use the default, or set it to any sound name.
Prefer code? Play sounds imperatively:
import { play } from "cuelume"; await navigator.clipboard.writeText(text); play("success"); play("success", { volume: 0.4 }); // quieter for this play only
Need sound preferences? Your app owns the settings; Cuelume only applies them:
import { setEnabled, setVolume } from "cuelume"; setVolume(0.7); // global multiplier, clamped to 0–1 setEnabled(false); // future play attempts become no-ops setEnabled(true); // enable playback again
Cuelume starts enabled at full volume and does not read or write storage.
#Sounds
| Name | Character | Suggested use |
|---|---|---|
chime |
Soft two-note ascending bell | Default hover |
sparkle |
Quick four-note twinkle | Playful accents |
droplet |
Single note gliding down | Dismiss, collapse |
bloom |
Warm slow swell | Reveal, expand |
whisper |
Soft hush with a falling tone | Tooltips and quiet previews |
tick |
Crisp instant tick | Nav and menu hover |
press |
Dull muted knock | Pointer down |
release |
Brighter springy tick | Pointer up |
toggle |
Mechanical click-clack | Switches, tabs |
success |
Warm three-note confirmation | After an action succeeds (e.g. copy to clipboard) |
error |
Soft knock and descending refusal | Recoverable errors |
page |
Papery flick with a glass tick | Pages, galleries, carousels |
loading |
Brief unresolved rising shimmer | User-initiated work starting |
ready |
Rising lock-on with a clear resolve | Content or system ready |
pulse |
Compact synthetic chirp | Primary buttons and controls |
scan |
Fast three-step locator signal | Menus and secondary buttons |
arrival |
Rising harmonic portal | Client-side page arrivals |
#API
import { play, bind, setEnabled, setVolume, sounds, type SoundName } from "cuelume";
play(name?: SoundName, options?: { volume?: number })— play a sound immediately. Defaults to"chime";options.volumecontrols this play only.bind(root?: ParentNode)— delegate alldata-cuelume-*interactions underroot(defaults to the whole document). Idempotent and handles elements added later.setEnabled(enabled: boolean)— enable or disable future playback. Does not persist the preference or stop sounds already playing.setVolume(volume: number)— set the global volume for future playback, clamped to0–1. Non-finite values are ignored and preferences are not persisted.sounds— the list of all sound names.SoundName— union type of the seventeen sound names.
#Defaults that behave
- Pointer-aware. Hover requires a fine mouse pointer. Press and release support mouse, touch, and pen; toggle follows native click activation, including keyboard.
- Hover repeat guard. Hover sounds are globally throttled to one every 150ms, so sweeping across a menu stays quiet.
- Audible without clipping. One shared boosted output stage keeps sounds clear, with native compression protecting overlapping cues.
- One lazy
AudioContext. Shared across all sounds, created on first use. - Autoplay-friendly. Attempts to resume suspended audio without surfacing errors when a browser blocks it.
- SSR-safe. Importing on the server is a no-op.
- Safe fallback. Invalid runtime names and unavailable or blocked Web Audio make
play()a silent no-op. - Dynamic, idempotent binding.
bind()never double-attaches listeners, and later DOM additions, removals, and clones work automatically.
#Frameworks
Cuelume works anywhere HTML does — plain pages, Astro, React, Vue. Call bind() once after the DOM is ready. Delegated listeners keep working when components or routes replace descendants.
React:
useEffect(() => {
bind();
}, []);
Astro (with view transitions):
import { bind, play } from "cuelume"; bind(); document.addEventListener("astro:page-load", () => play("arrival"));
Browsers block audio on a fresh visit until the user interacts with the page. The arrival cue therefore plays on client-side navigations after that first interaction.
#License
MIT