UI sound effects, performed live.
Comics had to draw the KA-CHUNK. Your buttons get to make it. Foley is a JavaScript library of 28 cues, synthesized with Web Audio the moment someone clicks, types, or toggles. No audio files, no dependencies.
$ npm install @foleyjs/coreI removed the entire visual UI. The app is sound only now. Revenue tripled.
We A/B tested ‘thock’. The B group unionized.
My cat answers to ‘chime’ now. I no longer control this household.
The ‘denied’ thud healed something in me.
I played ‘complete’ at my wedding. Even the CI pipeline wept.
I put it on my smart fridge. ‘warning’ plays at 2am. I’ve lost ten pounds.
28 cues in 7 families. Press any of them.
Each family has its own material: noise for touch, glides for motion, harmonics for news. Every cue has a key, so you can also just start typing.
Keyboard mode: 1–0, Q–P, A–K. The download button on each cue saves a 16-bit .wav with your current transpose, space, and theme, ready for Figma, video, or a native app.
Hear it on real controls.
A cue sounds different on a grid of samples than on a button you meant to press. Every control here uses the same attributes and calls you would ship.
Two-part press
Down and up are different sounds, so a click feels like a mechanism. Hold it: the release waits for you.
Toggle
A click and a clack. On and off are siblings, not twins, so you hear the state without looking.
Hover ticks
A grain of feedback as the pointer crosses each option. Turn on “play cues on hover” above to hear it.
Typing
A soft mechanical thock per keystroke and a flourish on Enter. Try writing a sentence.
Notifications
Toasts sound because they mounted. The page never calls play() for them; observe() hears them arrive.
Progress
Ticks while the work happens, a rise as it finishes, then the arrival chord. Start an upload.
Stepper
The same tick, re-pitched with the count, so quantity has a contour. Go past the limits to hear “denied”.
Slider
Detent ticks that rise in pitch with the value: the sound of a thumb wheel.
Not your sound? Redraw it.
Four plain dials reshape any cue while you listen. Export a .wav or JSON, share it as a link, or apply it site-wide as a sound set your app loads in one line.
Space replays. Exports honor your transpose, space, and theme, and designs live in the URL, so a copied link carries the sound.
Install, add an attribute, bind. That's the job.
Foley ships as one ES module. Attributes cover the common cases, play() covers the rest, and the React and Vue bindings wrap the same core.
Install
$ npm install @foleyjs/core
Mark up and bind
<!-- one attribute per behavior -->
<button data-foley-press data-foley-release>Save</button>
<a data-foley-hover="tick">Docs</a>
<button data-foley-toggle>Dark mode</button>
<input data-foley-type="thock">
<!-- and things nobody clicked -->
<div data-foley-enter="bubble">Saved</div>
import { bind, observe, play, set } from "@foleyjs/core";
bind(); // wires every data-foley-*
observe(); // …and elements appearing or changing state
set({ volume: 0.7, // 0 – 1
transpose: 0, // semitones, ±12
space: 0.2, // reverb send
theme: "glass" }); // a name, or your sound set object
play("complete"); // any of the 28 cues
set({ theme: brandSet }); // load your identity from the designer
play("tick", { pitch: 4, volume: 0.3 });
const wav = await toWav("chime"); // Blob, rendered offline
In React or Vue
// npm install @foleyjs/react
import { useFoley } from "@foleyjs/react";
function App() {
const { play } = useFoley({ volume: 0.7 });
return <button => play("success")}>
Save
</button>;
}
// npm install @foleyjs/vue
import { FoleyPlugin } from "@foleyjs/vue";
app.use(FoleyPlugin, { theme: "soft" });
<!-- then in any template -->
<button v-foley="'success'">Save</button>
<a v-foley:hover="'tick'">Docs</a>
Questions people actually ask.
The short answers. The docs have the long ones, and agents.md has the version for your coding agent.
Does Foley ship audio files?
No. Every cue is built at play time from Web Audio oscillators and filtered noise, so there is nothing to download, cache, or license.
How do I add a sound to a button?
Add an attribute such as data-foley-press and call bind() once at startup. It delegates from the root, so markup rendered later sounds too, and hover also fires on keyboard focus.
Will UI sounds annoy my users?
Foley is built not to. A master limiter caps loudness, a 60 ms cooldown stops repeats from stacking, pitch is humanized up to 30 cents, loops go quiet in background tabs, and set({ muted: true }) silences everything.
Does it work with React and Vue?
Yes. @foleyjs/react provides a useFoley() hook and @foleyjs/vue a plugin and a v-foley directive. Both wrap the same core, so cues and sound sets carry over.
Can I design my own sounds?
Yes. Cues are data. Edit one in the designer above or with getSpec() and playSpec(), then ship a sound set: your product's sonic identity as JSON.
Why does nothing play until I click?
Browsers block audio until a user gesture. bind() installs a one-time unlock listener, and play() resumes the audio context on its own.
That's the whole library.
One module, 28 cues, and a designer for the sounds it doesn't have yet. Your interface has been miming long enough.
$ npm install @foleyjs/core