React and TypeScript primitives for serious browser audio apps.
Build browser audio interfaces in React without fighting AudioContext.
webaudio-kit is a small browser audio toolkit for React apps that need tone
generation, frequency sweeps, noise bursts, volume controls, stereo panning,
pitch helpers, and analyser data without hand-managing raw Web Audio node
lifecycles.
It is intentionally scoped to safe procedural UI audio. If you need a full synth engine, transport, effects chain, AudioWorklets, or deep custom routing, read the scope and limitations guide before choosing the package.
npm install @webaudio-kit/core @webaudio-kit/react
pnpm add @webaudio-kit/core @webaudio-kit/react
yarn add @webaudio-kit/core @webaudio-kit/reactimport { AudioProvider, useTone } from "@webaudio-kit/react";
function App() {
return (
<AudioProvider>
<ToneButton />
</AudioProvider>
);
}
function ToneButton() {
const tone = useTone({ frequency: 440, gain: 0.12, durationMs: 500 });
return <button onClick={() => void tone.play()}>Play 440Hz</button>;
}Optional CLI helper:
npx @webaudio-kit/cli@latest agent-brief
pnpm dlx @webaudio-kit/cli agent-brief
yarn dlx @webaudio-kit/cli@latest agent-briefThe packages target browser runtimes and Node >=20.19 for local tooling.
@webaudio-kit/react supports React >=18.3, ships as a client entry for
Next.js App Router projects, and declares @webaudio-kit/core as a peer
dependency so apps keep one explicit core version.
- Vite React starter - Run in StackBlitz
- Next App Router starter - Run in StackBlitz
- Incident Alert Console - Run in StackBlitz
- Audio test mode - Run in StackBlitz
Default starter: https://webaudio-kit.afaqrashid.com/new
For CodePen, JSFiddle, or a single static HTML page, load the core
package directly through esm.sh — no bundler required. Audio still
needs a user gesture to start playback:
<!doctype html>
<button id="play">Play 440Hz</button>
<script type="module">
import { playTone } from "https://esm.sh/@webaudio-kit/core";
const audioContext = new AudioContext();
document.getElementById("play").addEventListener("click", async () => {
await audioContext.resume();
playTone(audioContext, { frequency: 440, durationMs: 500, gain: 0.12 });
});
</script>The React package can be loaded the same way through
https://esm.sh/@webaudio-kit/react when used inside a CDN-style React
runtime such as https://esm.sh/react.
import { AudioProvider, useTone } from "@webaudio-kit/react";
function App() {
return (
<AudioProvider>
<ToneButton />
</AudioProvider>
);
}
function ToneButton() {
const tone = useTone({
frequency: 440,
gain: 0.2,
type: "sine",
});
return (
<button onClick={() => void tone.play()}>
{tone.isPlaying ? "Restart 440Hz" : "Play 440Hz"}
</button>
);
}Browsers require audio playback to begin from a user gesture. AudioProvider
therefore creates and resumes AudioContext lazily when a hook action such as
tone.play() runs from a click or similar interaction.
Use useAudioUnlock() when a product needs an explicit first-run control before
alert sounds or background UI cues feel reliable. The hook calls the provider
from the button click and exposes labels for idle, unlocking, suspended,
running, and failed unlock states.
import { useAudioUnlock } from "@webaudio-kit/react";
function EnableAudioButton() {
const audio = useAudioUnlock();
return (
<>
<button disabled={audio.isUnlocked} onClick={() => void audio.unlock()}>
{audio.isUnlocked ? "Audio enabled" : "Enable Audio"}
</button>
<span>{audio.status}</span>
{audio.error ? <span>unlock failed</span> : null}
</>
);
}Use pattern when a UI needs repeated cues such as beep-beep-beep alerts. The
handle controls the full scheduled pattern, including future voices.
const alertTone = useTone();
await alertTone.play({
frequency: 880,
durationMs: 120,
gain: 0.12,
type: "square",
pattern: { repeat: 3, gapMs: 90 },
});
alertTone.stop();Add a short envelope when the cue should fade in or out instead of gating abruptly:
await alertTone.play({
frequency: 880,
durationMs: 180,
gain: 0.12,
envelope: { attackMs: 8, releaseMs: 45 },
});Shape harsher cues with a lowpass filter and lightweight detuned voices:
await alertTone.play({
frequency: 660,
durationMs: 220,
gain: 0.15,
type: "sawtooth",
envelope: { attackMs: 8, releaseMs: 55 },
filter: { frequency: 1800, q: 0.7 },
voices: { count: 2, spreadCents: 10 },
});Use useAudioContext().stopAll() for panic buttons, alert acknowledge actions,
or route changes that must cancel already scheduled library playback. This is
different from setting master gain to 0: muting affects volume, while
stopAll() stops active tone, sweep, noise, repeated pattern, and test-mode
handles created through the React provider.
import { useAudioContext, useVolume } from "@webaudio-kit/react";
function AlertControls() {
const audio = useAudioContext();
const volume = useVolume();
return (
<>
<button onClick={() => audio.stopAll()}>Stop all cues</button>
<button onClick={() => void volume.setGain(0)}>Mute future output</button>
</>
);
}Use useVolumeControl() when a slider should read and write provider gain
directly. This avoids duplicate app state for volume controls, keeps safe bounds
in one place, and can persist a preference with storageKey.
import { useVolumeControl } from "@webaudio-kit/react";
function ControlledVolumeSlider() {
const volume = useVolumeControl({
maxGain: 0.5,
storageKey: "app-master-gain",
});
return (
<>
<input {...volume.inputProps} />
<span>{volume.gain.toFixed(2)}</span>
<button onClick={() => void volume.resetGain()}>Reset volume</button>
</>
);
}Keep app-level state only for product preferences outside audio output, such as whether a workspace is muted by policy. For normal master volume UI, provider gain should be the source of truth.
Use useAudioEngine() when a React screen needs custom or layered sounds but
should still route through AudioProvider master gain and analyser output. The
engine creates/resumes audio from the user action, returns stop handles, and
registers playback with provider stopAll().
import { useAudioEngine } from "@webaudio-kit/react";
function LayeredAlertButton() {
const engine = useAudioEngine();
async function playLayeredAlert() {
await engine.playTone({
frequency: 880,
durationMs: 160,
gain: 0.1,
type: "square",
});
await engine.playNoise({
durationMs: 120,
gain: 0.025,
type: "pink",
});
}
return (
<>
<button onClick={() => void playLayeredAlert()}>
Play layered alert
</button>
<button onClick={() => engine.stopAll()}>Stop all</button>
</>
);
}Use engine.withAudioRuntime() when custom Web Audio code needs the non-null
provider runtime and should connect through runtime.masterGain.
WaveformCanvas and SpectrumCanvas forward standard canvas attributes, so
you can keep the drawing backing buffer stable with width/height while using
CSS style for responsive layout. The backing buffer controls analyser drawing
resolution; CSS sizing controls how the canvas fits its container.
import { SpectrumCanvas, WaveformCanvas } from "@webaudio-kit/react";
function SignalPanel() {
return (
<div style={{ maxWidth: 720 }}>
<WaveformCanvas
backgroundColor="#10110f"
height={180}
idleStrokeColor="#394135"
lineWidth={2}
strokeColor="#c8ea3a"
style={{ width: "100%", height: 140 }}
width={720}
/>
<SpectrumCanvas
backgroundColor="#10110f"
barColor="#8ed8ff"
barCount={48}
barGap={2}
height={140}
idleBarColor="#394135"
minBarHeight={2}
style={{ width: "100%", height: 120 }}
width={720}
/>
</div>
);
}Keep controls that call hooks in a client component. The package entry includes
"use client", but your component still needs a user interaction to start
browser audio:
"use client";
import { AudioProvider, SpectrumCanvas, useTone } from "@webaudio-kit/react";
function Controls() {
const tone = useTone({ frequency: 440, gain: 0.15 });
return (
<>
<button onClick={() => void tone.play({ durationMs: 600 })}>
Play tone
</button>
<SpectrumCanvas />
</>
);
}
export function AudioIsland() {
return (
<AudioProvider>
<Controls />
</AudioProvider>
);
}@webaudio-kit/coreplayTone()playFrequencySweep()playNoise()dbToGain()gainToDb()clampFrequency()midiToFrequency()frequencyToMidi()frequencyToNoteName()
@webaudio-kit/reactAudioProvideruseAudioContextuseAudioUnlockuseAudioEngineuseToneuseFrequencySweepuseNoiseuseAudioTestModeuseVolumeuseVolumeControluseAnalyserWaveformCanvasSpectrumCanvascreateDefaultAudioTestModeSteps
@webaudio-kit/cliwebaudio-kit agent-brief
apps/demo- tone generator
- 250Hz to 8000Hz sweep
- white, pink, and brown noise bursts
- reusable analyser waveform and spectrum canvases
apps/site- public landing page
- docs overview
- Vercel deployment config
pnpm install
pnpm test
pnpm typecheck
pnpm build
pnpm bench
pnpm site:dev
pnpm site:build
pnpm demo:dev
pnpm demo:qa
pnpm examples:check
pnpm smoke:pack
pnpm release:check
pnpm release:check:full
pnpm release:verify-tag v1.5.1
pnpm release:notes v1.5.1
pnpm release:dry-run
pnpm verifyThe public site runs on http://127.0.0.1:4173. The audio demo runs on
http://127.0.0.1:5173.
pnpm demo:qa runs the demo through Chromium, Firefox, and WebKit with
Playwright, then writes a demo screenshot, WebM, and GIF under docs/assets/.
pnpm smoke:pack packs all publishable packages and imports them from a clean
temporary app. It also runs the CLI bin from the packed tarball.
pnpm examples:check builds the standalone Vite React, Next App Router, plain
React, and compatibility examples from packed package tarballs so framework
integration stays honest.
pnpm bench runs local performance benchmarks for math helpers, playback graph
scheduling, analyser frame processing, and React audio hooks. Treat benchmark
numbers as local trend signals, not release gates. See the
public benchmark guide
and Markdown benchmark guide for telemetry-free usage
notes.
pnpm release:check runs the full verification gate, package smoke check, and
standalone example builds used before tagging a release.
pnpm release:check:full adds browser demo QA on top of the release check. Run
it before publishing when Playwright browsers and ffmpeg are installed.
pnpm release:verify-tag is the same stable-semver tag guard used by the
GitHub publish workflow. It rejects prerelease tags, package version drift,
private packages, missing public publish config, and mismatched repository
metadata.
pnpm release:notes v1.5.1 prints the GitHub Release notes generated from
CHANGELOG.md, including npm links for every published package in that version.
pnpm release:dry-run rebuilds and smoke-tests the package tarballs, checks npm
for already-published versions, then runs npm publish --dry-run in the same
package order used by the tag-gated publish workflow.
CHANGELOG.md is the source of truth for public release notes. Every stable
tag maps to a changelog section, a GitHub Release, and the same workspace
version across:
@webaudio-kit/core@webaudio-kit/react@webaudio-kit/cli
Package READMEs link npm users back to the full changelog and GitHub Releases,
and each package tarball includes a CHANGELOG.md copy so the version history
travels with the package.
Publishing is triggered by stable semver tags such as v1.0.0. The GitHub
workflow uses Node 24, npm trusted publishing with provenance, release tag
verification, workspace verification, dependency audit, package smoke testing,
ordered package publishing, and GitHub Release creation from CHANGELOG.md.
Npm trusted publishing is configured for the i-afaqrashid/webaudio-kit
repository, .github/workflows/publish.yml workflow, and npm GitHub
environment. Token publishing is not part of the normal release path.
The intended npm packages are:
@webaudio-kit/core@webaudio-kit/react@webaudio-kit/cli
Default playback gain is intentionally quiet at 0.2. Keep volume low when
testing headphones or hearing-test-style prototypes.
This library is for building browser audio interfaces and prototypes. It is not a certified audiology or medical testing system.
- Product plan
- Publicity plan
- Technical docs
- Hooks vs Core
- Scope and limitations
- Markdown docs fallback
- LLM docs index
- Live demos
- Architecture
- API reference
- Recipes
- Browser audio guide
- Docs fetch access
- AI agent brief CLI
- Examples
- Example apps
- Run in StackBlitz
- Design references
- Deployment
- Safety
- Troubleshooting
- Testing
- Release checklist
- Launch checklist
- Governance
- Security policy
- Support
- Open source expectations
- Code of conduct
