Cuelume: seventeen synthesized UI sounds behind one data attribute
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.
At a glance
- What is it?
- Cuelume is a curated sound palette for web interfaces, not an audio engine. It ships seventeen Web Audio cues, an ESM-only package, and a bind() call that turns data attributes into interaction feedback.
- Who is it for?
- Adopt Cuelume if you want interaction feedback in a plain HTML page, Astro site, React app or Vue app without authoring or hosting audio files, and you accept an ESM-only package and a browser-only playback path. Do not adopt it if you need CommonJS require(), a sound design tool with waveform editing, or persisted user preferences, because Cuelume starts enabled at full volume and, per the README, does not read or write storage.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 19, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What Cuelume solves for interface builders
Most web interfaces are silent by default. Adding feedback sound usually means finding a library of audio files, hosting them, wiring preload logic, and then tuning each clip so a hover does not sound like a door slamming. Cuelume skips the asset pipeline entirely. Its README describes it as "a curated sound palette, not an audio engine," and the package.json description repeats the claim: sounds are synthesized live with zero runtime dependencies. There is no engine to configure, no node graph to assemble, no sample pack to license.
The intended audience is narrow and identifiable. If you are building buttons, links, toggles, menus or confirmation moments and you want a defensible default rather than a bespoke sound identity, this is the shape of tool you would reach for. The seventeen named sounds carry suggested uses: tick for nav and menu hover, toggle for switches and tabs, success for an action that completed, error for a recoverable failure, page for carousels and galleries. That mapping is the product. A team that already employs a sound designer is not the audience, because the palette is fixed and the README documents no way to register your own samples.
How bind(), delegation and the shared AudioContext fit together
The mechanism is event delegation plus a single lazily created AudioContext. You mark up elements with data-cuelume-* attributes, then call bind() once. The README states that bind() delegates all matching interactions under a root node, defaults to the whole document, is idempotent, and handles elements added later. That last property matters in component frameworks: because listeners sit on the root rather than on each element, replacing a subtree does not require rebinding.
Four attributes map to four DOM events. data-cuelume-hover fires on pointerenter, data-cuelume-press on pointerdown, data-cuelume-release on pointerup, and data-cuelume-toggle on click. Leave the value empty for the default sound or name any of the seventeen. Two guards keep the result from becoming noise. Hover is restricted to fine mouse pointers, so touch devices do not trigger it, and hover sounds are globally throttled to one every 150ms. Press, release and toggle are described as working across mouse, touch, pen and, for toggle, keyboard activation through native click.
Output goes through what the README calls one shared boosted output stage, with native compression protecting overlapping cues. Playback is deliberately forgiving. Invalid runtime names, unavailable Web Audio and blocked autoplay all resolve to a silent no-op, and importing the module on the server is a no-op as well. The package declares sideEffects false and exposes only an import condition, so a bundler can drop it from server bundles without a wrapper.
Installing Cuelume and wiring a first button
Installation is a single npm command. The package is ESM-only, so the README is explicit that CommonJS require() is not supported; use native import or an ESM-compatible bundler.
npm install cuelumeMarkup comes next. Attributes are the whole configuration surface, and an empty value selects the default sound for that event. The README gives this example.
<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 bind once, after the DOM is ready. In a React component the README shows the call inside a mount effect.
useEffect(() => {
bind();
}, []);For a one-off cue with no attribute involved, play() is the imperative path. The README's clipboard example awaits the write and then plays success, optionally at a reduced volume for that single play.
import { play } from "cuelume";
await navigator.clipboard.writeText(text);
play("success");
play("success", { volume: 0.4 });What you should see after bind() runs: hovering the Docs link with a mouse produces the tick, pressing and releasing the Save button produces press then release, and the toggle button produces the mechanical click-clack on click. On a fresh page load with no prior interaction, browsers block audio, so the first cue may be silent until the user clicks or taps something.
The limits: ESM-only, browser-only, and no persistence
Three constraints are documented clearly enough that they should shape your decision. First, the package is ESM-only. package.json exposes a single "." entry with types and import conditions and no require field, so a CommonJS consumer cannot load it without a wrapper or an ESM-capable toolchain. Second, playback is browser-only. Server-side imports are safe, but the README says sound only runs in the browser, which makes the library useless for anything generating audio outside a DOM.
Third, and easiest to miss, Cuelume does not remember anything. setEnabled and setVolume change future playback only. The README states that Cuelume starts enabled at full volume and does not read or write storage, that setEnabled does not persist the preference, and that setVolume ignores non-finite values and does not persist either. If you want a mute switch that survives a reload, your application owns that state and must call the setters on load. The same applies to stopping sound already in flight: setEnabled(false) affects future attempts, not cues currently playing.
Autoplay policy is the other failure mode. Browsers block audio until the user interacts with the page. The Astro example in the README acknowledges this directly, noting that an arrival cue plays on client-side navigations after that first interaction. A landing page that tries to greet a visitor with sound on first paint will simply be silent, and Cuelume will not surface an error to tell you why.
Cuelume compared with Howler.js and raw Web Audio
The obvious alternative for web audio is Howler.js, and the difference is architectural rather than cosmetic. Howler is a playback engine for assets you supply: you register sound files, it manages loading, sprites, pooling and cross-browser quirks. Cuelume inverts that. There are no assets to supply, because the seventeen cues are synthesized at runtime, and the library's job is to map DOM events to those cues. Choosing between them is really a question of whether you want a sound design pipeline or a fixed palette. If your product's identity depends on specific recorded sounds, Howler is the right layer and Cuelume is not.
The other alternative is the Web Audio API directly. That gives you full control over oscillators, envelopes and routing, at the cost of writing and maintaining all of it. Cuelume's value proposition is that it has already made those decisions, including the unglamorous ones: a shared boosted output stage, compression across overlapping cues, a 150ms hover throttle, and a fallback that turns blocked or unavailable audio into silence rather than a console full of errors. Whether that trade is worth it depends on how much you care about the specific character of each sound. The README documents the palette's character in a table, but it does not document per-sound tuning parameters, so you cannot adjust the timbre of chime or success without forking.
Licence, maintenance and upgrade cost
Cuelume is MIT licensed, and both the README and package.json state it. That permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. This is not legal advice; check the LICENSE file in the repository for the exact terms before relying on it.
The package version in package.json is 0.2.2, and no releases were retrieved for this review. The repository is not archived, and the last push was on 2026-08-04, so it has been quiet for roughly six weeks as of this writing. That is recent enough that the project is not abandoned, but a 0.x version number signals that the API is not yet frozen. The surface is small, which limits upgrade exposure: play, bind, setEnabled, setVolume, sounds and the SoundName type. If a breaking change lands, it would most likely touch attribute semantics or the sound list rather than a deep configuration object.
The build is plain TypeScript compiled with tsc to dist, with a clean step and a prepack hook that runs the build before publishing. Dev dependencies list only typescript. The test script builds first and then runs node --test, and the repository has a test directory. Because the package ships only dist and declares sideEffects false, the upgrade cost for a typical consumer is one npm install and a read of the changelog, assuming you have not wrapped the API in your own abstraction.
Editorial conclusion
Adopt Cuelume if you want interaction feedback in a plain HTML page, Astro site, React app or Vue app without authoring or hosting audio files, and you accept an ESM-only package and a browser-only playback path. Do not adopt it if you need CommonJS require(), a sound design tool with waveform editing, or persisted user preferences, because Cuelume starts enabled at full volume and, per the README, does not read or write storage. Verify first that your bundler resolves the import-only export map in package.json, and that your test runner tolerates the server-side no-op import.
Frequently asked questions
What is Cuelume in simple terms?
Cuelume is a curated sound palette for web interfaces. It provides seventeen interaction sounds synthesized live with Web Audio, applied by adding data attributes and calling bind() once.
How do I install and set up Cuelume?
Run npm install cuelume, add data-cuelume-press, data-cuelume-release, data-cuelume-hover or data-cuelume-toggle attributes to your markup, then import and call bind() after the DOM is ready. The package is ESM-only, so CommonJS require() is not supported.
Can Cuelume remember a user's volume or mute preference?
No. The README states that Cuelume starts enabled at full volume and does not read or write storage, and that setEnabled and setVolume do not persist preferences. Your application owns the settings and must apply them on load.
Community notes