CLI tool
raphaelsalaja/audio avatar
raphaelsalaja/audio

@web-kits/audio: UI sounds as plain objects instead of audio files

Declarative audio synthesis for the web

589 stars20 forksTypeScriptMIT

At a glance

What is it?
@web-kits/audio is a TypeScript library for the web where a sound is described as a plain object of source, envelope and gain, and played with a single function call, with a React hook for loading patches and a CLI for installing community sound patches from GitHub. It is around 11 kB gzipped and tree-shakeable.
Who is it for?
Adopt @web-kits/audio if you need interface sounds in a web app and would rather describe them than ship audio files, because a sound becomes a few lines of TypeScript that a designer can read and a developer can tweak. Do not adopt it for musical or spoken content, since this is a synthesis library for short interface sounds rather than a player for assets.
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 last received commits 47 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A sound as an object, not a file

@web-kits/audio is described as declarative audio synthesis for the web, where you describe sounds as plain objects and play them with one function call. The tagline above the install command is the pitch in a few words: about 11 kB gzipped, tree-shakeable, works with vanilla JS or React.

The quick start makes the shape of it obvious. You define a sound with defineSound, giving it a source, an envelope and a gain, and you get back something callable:

ts
import { defineSound, ensureReady } from "@web-kits/audio";

const click = defineSound({
  source: { type: "sine", frequency: { start: 1200, end: 300 } },
  envelope: { attack: 0.001, decay: 0.06, sustain: 0, release: 0.03 },
  gain: 0.3,
});

await ensureReady();
click();

That example says a lot. The source is a sine oscillator whose frequency sweeps from 1200 down to 300, which is what makes a click sound like a click rather than a beep. The envelope is the four stage ADSR shape, with an attack of 1 millisecond, a decay of 60, no sustain and a release of 30, and the gain is 0.3.

None of that is a file. There is no audio asset to load, no decodeAudioData, no buffer to manage, and no request that can fail or arrive late. The sound is synthesised at play time from numbers, which means it is a few hundred bytes of source and it scales to any resolution.

The ensureReady call before playing is the one piece of ceremony, and it is there because the Web Audio API requires a user gesture before an AudioContext can start. A library that hides that would be hiding a bug.

The library is TypeScript, MIT licensed, and the two releases are @web-kits/audio 0.2.0 from 2026-09-05 and 0.1.0 from 2026-04-20.

React gets a hook, and patches come from JSON

The React integration is a single hook, and it introduces the idea of a patch, which is the part of the design that matters most.

tsx
import { usePatch } from "@web-kits/audio/react";

function App() {
  const patch = usePatch("/patches/core.json");

  return (
    <button onClick={() => patch.play("click")} disabled={!patch.ready}>
      Click me
    </button>
  );
}

The hook takes a URL and returns a patch object with a play method and a ready flag, and the button is disabled until the patch has loaded. That ready flag is the detail to notice, because it is what stops a user clicking a sound that has not arrived yet, and it is the kind of state a naive integration usually forgets.

A patch is a named collection of sounds. The example plays a sound called click from a patch loaded from /patches/core.json, which means the naming of interface sounds becomes a shared vocabulary rather than a string literal scattered through components. One file defines the sound of a click, and every component that wants a click asks for the same one.

That is the point where this stops being a toy and becomes a design tool. A patch file is a piece of design that a sound designer can edit, that a developer can review in a diff, and that a product can standardise on.

The React entry point is a separate import path, @web-kits/audio/react, which is the usual convention for a library that ships a framework-specific binding without forcing the framework on you, and the library is usable from vanilla JavaScript as well.

A CLI for browsing and installing community patches

The CLI extends the patch idea into a distribution mechanism, and the commands are the clearest statement of what it does.

bash
# Browse the registry
npx @web-kits/audio add

# Install from a GitHub repo
npx @web-kits/audio add user/repo

# Create a new patch
npx @web-kits/audio init

# List installed patches
npx @web-kits/audio list

So there is a registry to browse, patches can be installed from a GitHub repository by owner and name, new patches can be created locally, and installed ones can be listed. The patches end up in a project directory, and the monorepo structure shows where the first party ones live, in a .web-kits directory described as first-party sound patches and generated code.

The readme credits the inspiration: patch discovery and the CLI are inspired by skills.sh by Vercel. That is a useful pointer, because it tells you the model is the one where a registry of installable capabilities is discovered and installed by name, and that the author is consciously borrowing the ergonomics rather than inventing them.

The core library and the CLI are published together as a single package, packages/audio, which is the one published to npm. The rest of the monorepo is the web app that serves the documentation, the registry and the homepage, the shared UI components, and the patches themselves.

The library itself is built on the Web Audio API, which the readme lists as the first reference, and the tree-shakeability claim follows from that. If you use only defineSound and never touch the oscillator types or the patch loader, the bundler drops the rest, and for a library whose whole purpose is to be present in every page, a small tree-shaken footprint is the difference between adding it and not.

What the Web Audio API does and does not give you

It helps to be precise about what the library is standing on, because the Web Audio API is more capable than most people assume and less convenient than a media element.

The library is built on the Web Audio API, and the quick start example uses two of its primitives directly. The source of type sine with a frequency that sweeps from a start value to an end value is an oscillator node whose frequency is ramped, and the envelope is a gain node shaped over time. So a click is a sine sweep with a fast attack and a short decay, and the synthesis happens in the audio graph rather than in a sample.

The advantage over an audio file is not just size, although 11 kB for a whole sound library is a good argument. It is that the parameters are editable, that the sound can be retuned without regenerating an asset, and that there is no decoding step, so a sound is available on the first frame after a gesture rather than after a network round trip.

The cost is that you are limited to what the graph can express. A spoken word or a musical recording is not a synthesis, it is sampled content, and no amount of oscillator nodes will produce it. That is why this library is positioned for interface sounds, and the project topics agree, listing sound-design, ui-sounds and synthesizer. The scope is the short percussive and tonal sounds that a user interface makes: a click, a tick, a chime, an error tone.

The other thing the Web Audio API imposes is the gesture requirement, which is what ensureReady is for. Browsers will not start an AudioContext without a user interaction, and the rule exists because a page that plays sound on load is a page people close. A library that abstracts the context away has to either swallow the promise or expose the wait, and this one exposes it.

The monorepo, the tooling and the state of the project

The repository is a Turborepo and pnpm workspace, and the structure tells you how the pieces relate.

The root package.json is private and named audio, and every script delegates to turbo. The build, dev, lint, test and typecheck are all turbo run calls, and the publish script is a chain of turbo build, turbo lint, changeset version and changeset publish, so releases are driven by changesets and the version bump is derived from the commit messages rather than typed by hand.

The tooling is current. Biome at 2.4.12 for linting, Prettier for the markdown and MDX files, commitlint with the conventional config, husky for the hooks, TypeScript 6.0.3, and pnpm 9.15.4 as the package manager with Node 20 or newer required.

One thing stands out in the manifest and is worth understanding rather than skipping. There is a long list of pnpm overrides pinning every Remotion package to the same version, 4.0.450, across the whole Remotion family including the platform-specific compositor binaries for Darwin, Linux and Windows, in both the gnu and musl Linux variants. Remotion is a React video framework, so it is a dependency of the docs and demo media pipeline rather than of the audio library, and pinning the entire family to one version is what keeps those native compositor binaries consistent with each other.

There is a skills directory with a skills-lock.json, which is the registry content, and the .web-kits directory for first party patches, alongside a SECURITY.md, a CODE_OF_CONDUCT.md and an assets directory for the icons.

The project state is early but not dormant. Two releases, 0.1.0 in April 2026 and 0.2.0 in September 2026, with the last push on 2026-09-05, and the documentation lives at audio.raphaelsalaja.com.

Editorial conclusion

Adopt @web-kits/audio if you need interface sounds in a web app and would rather describe them than ship audio files, because a sound becomes a few lines of TypeScript that a designer can read and a developer can tweak. Do not adopt it for musical or spoken content, since this is a synthesis library for short interface sounds rather than a player for assets. Before you commit, check how your app handles the browser autoplay policy, since sound only plays after a user gesture, and read the patching documentation before designing a shared sound vocabulary, because the patch format is what the community registry shares.

Frequently asked questions

What is @web-kits/audio?

It is a TypeScript library for declarative audio synthesis on the web, where a sound is described as a plain object with a source, an envelope and a gain, and played with a single function call. It is about 11 kB gzipped, tree-shakeable, and works with vanilla JavaScript or React.

How do I install and use @web-kits/audio?

Run npm install @web-kits/audio, then define a sound with defineSound and call it. Because the Web Audio API requires a user gesture before audio can start, you await ensureReady before the first play.

What is a patch in @web-kits/audio?

A patch is a named collection of sounds loaded from a JSON file, so a sound such as a click has one shared definition rather than a literal repeated in every component. The React hook usePatch loads a patch by URL and returns a play method along with a ready flag, which is why the example disables the button until the patch has loaded.

How do I find and install community sound patches?

With the CLI. Run npx @web-kits/audio add to browse the registry, npx @web-kits/audio add user/repo to install from a GitHub repository, npx @web-kits/audio init to create a new patch and npx @web-kits/audio list to see what is installed. The readme credits skills.sh by Vercel as the inspiration for the discovery and CLI model.

What is the size and release status of @web-kits/audio?

The readme states about 11 kB gzipped and tree-shakeable. The two releases so far are @web-kits/audio 0.2.0 from 2026-09-05 and 0.1.0 from 2026-04-20, so the package is pre-1.0 and the API may still change.

Official sources

  1. License: MIT
  2. Project website
  3. raphaelsalaja/audio on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/raphaelsalaja-audio.svg)](https://hysenlabs.com/projects/raphaelsalaja-audio)

Was this useful?