npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@foleyjs/core

v2.7.0

Published

Sound effects for the interface, performed live. 28 interaction cues synthesized with Web Audio — zero dependencies, zero audio files.

Readme

foley

npm CI license

Sound effects for the interface, performed live. Play the demo →

Foley is a tiny, dependency-free library of 28 interaction sounds, named for the film artists who perform footsteps and door-latches in sync with the picture. It does the same for your interface: every cue is synthesized with Web Audio at the instant of the action. No audio files, no network requests, no build step.

  • 28 cues in 7 families — pointer, press, toggle, feedback, notify, motion, state
  • 4 themes — default, soft, mechanical, glass — or your own transform, or a full sound set
  • Cues are data — edit any cue's layers with getSpec()/playSpec(), or visually in the Cue Designer
  • 10.9 kB, zero dependencies, one ES module
  • Placed in spacepan or 3D pos per play, or set({ localize }) to pan every bound cue to where its element sits on screen
  • Defensive by design — master limiter, 60ms per-cue cooldown, ±30-cent humanization; stop() handles, looping, and ducking built in
  • WAV export — any cue, any custom design, or all 28 as an audio sprite with an offset map

Install

npm install @foleyjs/core

Published under the foleyjs org — the bare name foley is blocked by npm's package-name similarity rules. Framework bindings will join as @foleyjs/react and friends.

Or skip the install entirely and vendor the single file: copy src/foley.js into your project.

Quickstart

Mark up anything that should make a sound, then bind once at startup.

<button data-foley-press data-foley-release>Save</button>
<a data-foley-hover="tick">Docs</a>
<button data-foley-toggle aria-pressed="false">Dark mode</button>
<input data-foley-type="thock">
import { bind, play, set } from "@foleyjs/core";

bind();                          // wires every data-foley-* attribute
set({ volume: 0.7, theme: "default" });

play("success");                 // programmatic cues for things the user didn't click
play("tick", { pitch: 4, volume: 0.3 });

Browsers require a user gesture before audio can start; bind() installs a one-time unlock listener for you, and play() resumes the context automatically.

Declarative attributes

| Attribute | Fires on | Default cue | | --- | --- | --- | | data-foley-hover | pointerenter | tick | | data-foley-press | pointerdown | press | | data-foley-release | pointerup | release | | data-foley-click | click | tap | | data-foley-toggle | click (reads aria-pressed) | on / off | | data-foley-type | keydown (Enter plays complete) | thock |

Every attribute accepts a cue name as its value to override the default.

The 28 cues

| Family | Cues | | --- | --- | | Pointer | tick hover glide pop | | Press | press release tap thock | | Toggle | on off switch latch | | Feedback | success error warning denied | | Notify | chime ping bell bubble | | Motion | swoosh whoosh drop rise | | State | loading ready complete sparkle |

API

import { play, bind, set, get, toWav, unlock, getAnalyser, on, cues, families, themes, version } from "@foleyjs/core";
  • play(name, { pitch?, volume?, loop?, every?, pan?, pos? }) — play a cue; returns { stop() }. With loop: true it repeats until stopped — ideal for loading states.
  • bind(root?) — wire all data-foley-* attributes under root (default document). Idempotent.
  • set({ volume?, transpose?, space?, muted?, hover?, theme?, duck?, localize? }) — update global settings. duck (0–1) temporarily attenuates everything, e.g. while a video plays. localize (0–1) pans every bound cue to its element's place on screen. theme accepts a name or a custom transform object ({ pitch, decay, send, ... }).
  • panFor(el) — the pan localize would derive for an element, for your own play() calls.
  • get() — snapshot of current settings.
  • toBuffer(name)Promise<AudioBuffer>: same offline render, raw — for envelope drawings, meters, or custom encoding.
  • getSpec(name) — a deep, editable copy of a built-in cue's layer spec.
  • playSpec(spec, { id?, pitch?, volume?, pan?, pos? }) — play a custom spec; it is validated and clamped first.
  • toWavSpec(spec) / toBufferSpec(spec) — offline-render a custom spec.
  • normalizeSpec(spec) — the validator itself, for checking untrusted specs (max 8 layers, all params clamped).
  • toSprite(gap?) — all 28 cues in one WAV plus a { name: { start, duration } } offset map, for game engines and audio-sprite players.
  • toWav(name)Promise<Blob>: offline-render a cue to 16-bit 44.1 kHz stereo WAV, honoring transpose, space, and theme. Exports are deterministic (no humanization drift).
  • unlock() — resume/create the AudioContext from a user gesture.
  • getAnalyser() — the engine's AnalyserNode for scopes and meters, or null before unlock.
  • on("play" | "unlock", cb) — subscribe to engine events; returns an unsubscribe function.
  • cues / families / themes / version — metadata for building your own pickers and playgrounds.

Placement

A cue can come from somewhere. pan puts it in the stereo field; pos puts it in 3D (HRTF, inverse distance) for WebXR and canvas scenes.

play("tick", { pan: -0.7 });              // over on the left
play("ping", { pos: [2, 0, -3] });        // up and to the right, a few metres out
set({ localize: 0.6 });                   // every bound cue pans to its own button

One setting and the interface stops sounding like it comes from a single point — a toolbar on the right clicks on the right, which is the thing a screen-shaped sound library can do that a game audio engine never bothered to.

Placement is fixed at the trigger: cues are ~200ms one-shots, over before anything could move, so there is no listener, no cones, and nothing to reposition mid-flight. The reverb send stays center — rooms don't pan, sources do. Exports (toWav, toSprite) stay centered too: position is a property of the performance, not of the sound, the same rule humanization follows.

Design your own cues

Every cue is data: an array of tone, noise, and cluster layers. Grab one, reshape it, play it:

import { getSpec, playSpec, toWavSpec } from "@foleyjs/core";

const mySound = getSpec("success");
mySound[2].f = 880;                  // raise the last note
mySound.push({ kind: "noise", at: 0.2, filter: "highpass", f: 6000,
               a: 0.01, d: 0.2, peak: 0.05, send: 0.4 });
playSpec(mySound);
const wav = await toWavSpec(mySound);

Or use the visual Cue Designer on the demo — edit with live playback, then export .wav/.json or share the design as a link.

Sound sets

A sound set is your product's whole sonic identity as one portable JSON object: a global character transform plus full replacement specs for the cues that matter most.

import { set, getSet } from "@foleyjs/core";

set({ theme: {
  name: "Acme",
  transform: { pitch: 0.9, decay: 1.3 },      // every cue, reshaped
  cues: { success: [/* layers */], error: [/* layers */] }  // these two, replaced
}});

get().theme;   // "Acme"
getSet();      // snapshot the active identity - JSON-safe, version it in your repo
  • getSet() returns the active identity; feeding it back through set({ theme }) is lossless.
  • Assigning any named theme replaces the whole identity, overrides included.
  • Overrides are validated like any spec (unknown cues dropped, params clamped).
  • Build one visually in the Cue Designer: design a cue, pick which built-in it replaces, "Use site-wide", then export the set or copy a set link.

Themes

One setting reshapes all 28 cues — waveforms, envelopes, brightness, noise character, and reverb:

set({ theme: "glass" }); // "default" | "soft" | "mechanical" | "glass"

Soft rounds every waveform and adds room. Mechanical halves the transients and dries the space — machined metal. Glass pitches up, rings the filters, and grows an inharmonic partial on every voice — the physics trick that makes struck glass sound like glass.

Engine behavior you get for free

  • A master limiter (DynamicsCompressor as brick-wall safety) so overlapping cues never clip.
  • A 60ms per-cue cooldown so hover storms and fast sliders stay musical instead of machine-gunning.
  • Humanization: each performance drifts up to ±30 cents in pitch and ±8% in level, applied to the whole cue at once — repeated ticks sound performed, not stamped.

Framework packages

  • React@foleyjs/react: const { play } = useFoley({ theme: "soft" })
  • Vue 3@foleyjs/vue: app.use(FoleyPlugin) then <button v-foley="'success'">
  • AI agentsagents.md: integration instructions for coding assistants

Both re-export everything from the core, share its version number, and declare it as a peer dependency.

Run the demo

The demo page (index.html) imports src/foley.js directly — it runs exactly what the package ships. ES modules need a server:

npm run demo        # or: npx serve .

Then open the printed URL. GitHub Pages works too: enable it on the repo root and the demo is live.

Development

npm install      # once per clone — the test suite imports the framework packages,
                 # which need react and the self-linked core from node_modules
npm test         # node --test: metadata integrity, settings, docs/types consistency, build
npm run build    # regenerate the single-file demo at dist/foley-demo.html
npm run docs     # render README.md into dist/docs.html (deployed at /docs.html)
npm run demo     # serve the demo locally

Contributions welcome — see CONTRIBUTING.md. Releases follow RELEASING.md. CI runs the tests on every push; merges to main deploy the demo to GitHub Pages (enable Pages with the "GitHub Actions" source in repo settings, once). Pushing a v* tag publishes all three packages to npm from CI via trusted publishing — no tokens. One-time setup: publish each package manually once, then in each package’s npm settings add a Trusted Publisher pointing at this repo and release.yml. Provenance is automatic.

License

MIT © eakbulut and Foley contributors.