Project Overview
React-Sounds delivers React hooks and components for adding audio feedback with minimal setup. It offers a large, categorized, lazy-loaded sound effects library and full control over playback and configuration.
Purpose
Enable developers to integrate sound effects into React applications without managing audio assets or low-level APIs.
When to Use
- Provide audio cues for user interactions (buttons, notifications, navigation).
- Enhance UX in games, educational apps, or interactive dashboards.
- Require on-demand loading to reduce initial bundle size.
Key Features
useSound
hook for triggering and controlling sounds- Predefined, categorized sound library (UI, alerts, confirmations)
- Lazy loading of audio assets to optimize performance
- Playback controls: play, pause, stop, volume, loop
- Global configuration via
SoundProvider
- Built-in UI components for common patterns
- TypeScript support with exported types
Core Exports
Developers integrate features by importing from react-sounds
:
import {
useSound,
SoundProvider,
playSound, // programmatic playback
stopSound, // stop by key
setGlobalVolume, // adjust volume for all sounds
SoundButton, // ready-to-use button component
SoundConfig, // type for custom sound definitions
} from 'react-sounds';
Supported Browsers
Built on the Web Audio API and HTML5 Audio. Works in all modern browsers:
- Chrome (v60+)
- Firefox (v55+)
- Edge (v79+)
- Safari (v11+)
License
MIT License — see LICENSE file for details.
Getting Started
Add audio feedback to your React app in under 30 seconds.
1. Install the package
npm install react-sounds
# or
yarn add react-sounds
2. Wrap your app in the provider
In your entry file (e.g. index.tsx
), import and wrap <SoundProvider>
around your root component:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { SoundProvider } from 'react-sounds'
ReactDOM.render(
<SoundProvider>
<App />
</SoundProvider>,
document.getElementById('root')
)
3. Trigger a sound with useSound
In any component, import useSound
, specify a sound key from the library (e.g. ui/notification
), then call play()
:
import React from 'react'
import { useSound } from 'react-sounds'
export default function NotificationButton() {
const { play, isLoaded, isPlaying } = useSound('ui/notification', {
volume: 0.7,
loop: false
})
return (
<button
disabled={!isLoaded}
onClick={() => {
play().then(() => console.log('Played')).catch(console.error)
}}
>
{isPlaying ? 'Playing…' : 'Play Notification'}
</button>
)
}
4. Quick trigger with SoundButton
For simple click sounds, use the built-in component:
import React from 'react'
import { SoundButton } from 'react-sounds'
export default function ClickMe() {
return (
<SoundButton sound="ui/click" volume={0.5}>
Click Me
</SoundButton>
)
}
Now you have a sound-enabled app! Explore useSoundOnChange
for state-driven triggers and useSoundEnabled
to toggle audio globally.
Core API & Usage
This section covers everything you can import from the aediliclabs/react-sounds package: context providers, hooks, and utility functions to load, play, and control sound in your React app.
SoundProvider
Wrap your app to enable global sound state and inject the sound context.
Import
import { SoundProvider } from "aediliclabs/react-sounds"
Props
• initialEnabled?: boolean // defaults to true
Usage
import React from "react"
import { SoundProvider } from "aediliclabs/react-sounds"
import App from "./App"
export default function Root() {
return (
<SoundProvider initialEnabled={true}>
<App />
</SoundProvider>
)
}
Global Sound Toggle
Use these hooks to read or update the global “sound on/off” flag.
useSoundEnabled
Returns boolean indicating whether sounds play.
import { useSoundEnabled } from "aediliclabs/react-sounds"
function MuteIndicator() {
const enabled = useSoundEnabled()
return <span>{enabled ? "🔊 Sounds On" : "🔇 Muted"}</span>
}
setSoundEnabled
Toggle global sound state.
import { setSoundEnabled } from "aediliclabs/react-sounds"
function MuteButton() {
return (
<button onClick={() => setSoundEnabled(false)}>
Mute All Sounds
</button>
)
}
Utility Functions
Call these directly to preload assets or fire-and-forget sounds outside of React components.
preloadSounds(names: LibrarySoundName[]): Promise<void[]>
Fetches audio blobs for the given names without starting playback.
import { preloadSounds } from "aediliclabs/react-sounds"
import type { LibrarySoundName } from "aediliclabs/react-sounds"
const critical: LibrarySoundName[] = ["ui/button_soft", "notification/success"]
useEffect(() => {
preloadSounds(critical)
.then(() => console.log("Sounds ready"))
.catch(console.error)
}, [])
playSound(name: LibrarySoundName, options?: SoundOptions): Promise
Plays a single instance of the named sound. Honors global mute flag.
import { playSound } from "aediliclabs/react-sounds"
async function notifySuccess() {
try {
await playSound("notification/success", { volume: 0.8 })
// Continue after sound ends
} catch (err) {
console.error("Playback failed", err)
}
}
SoundOptions
• volume?: number // 0.0–1.0
• rate?: number // playback speed multiplier
• loop?: boolean // repeat until stopped
useSound Hook
Fine-grained control of a named sound inside a component.
Import
import { useSound } from "aediliclabs/react-sounds"
import type { SoundOptions, LibrarySoundName } from "aediliclabs/react-sounds"
Signature
function useSound(
soundName: LibrarySoundName,
defaultOptions?: SoundOptions
): {
play(options?: SoundOptions): Promise<void>
stop(): void
pause(): void
resume(): void
isPlaying: boolean
isLoaded: boolean
}
Key behaviors
- Lazy-loads on first play or mount
- Unlocks audio context on first user interaction
- Returns a promise for non-looped sounds
- Auto-pauses if global mute is enabled
- Cleans up on unmount
Basic example
function ClickButton() {
const { play, isLoaded, isPlaying } =
useSound("ui/button_soft", { volume: 0.5 })
return (
<button
disabled={!isLoaded}
onClick={() => play().catch(console.error)}
>
{isPlaying ? "Playing…" : "Click me"}
</button>
)
}
Looping ambience
import { useEffect } from "react"
function RainLoop() {
const { play, stop, isLoaded } = useSound("ambient/rain")
useEffect(() => {
if (isLoaded) play({ loop: true, volume: 0.3 })
return () => { stop() }
}, [isLoaded])
return null
}
Practical Tips
- Preload critical sounds at startup with preloadSounds.
- Wrap your root in SoundProvider to support global on/off.
- Use useSoundEnabled/setSoundEnabled for in-app mute toggles.
- Always catch promise rejections from play()/playSound() to handle load/play errors.
- Reuse the same hook or playSound calls to share Howl instances and minimize memory.
CLI Tool Reference
Manage and explore the React-Sounds catalog or bundle sounds locally using the built-in CLI.
Listing Available Sounds (react-sounds-cli list
)
Display all sound keys defined in dist/manifest.json
, grouped by category.
Usage
bash
npx react-sounds-cli list
Example
# Show every available sound
npx react-sounds-cli list
# Filter by category (e.g. “ui”)
npx react-sounds-cli list | grep ui
Output
ui:
click
hover
notification:
success
error
game:
explosion
jump
Offline Sound Downloading (react-sounds-cli pick
)
Fetch specific sounds from the CDN and save them into local category folders for offline use.
Usage
bash
npx react-sounds-cli pick <sound-names...> [--output=
Arguments & Options
<sound-names...>
One or more manifest keys, e.g.ui/click
,notification/success
.--output=<dir>
Destination folder (default:./public/sounds
).
Environment Variable
REACT_SOUNDS_CDN
Override the default CDN base URL (https://reacticons.sfo3.cdn.digitaloceanspaces.com/v1
).
Behavior
- Reads
dist/manifest.json
to resolve each key’s CDN path. - Creates the output directory and subfolders (e.g.
ui/
,notification/
,game/
). - Streams each
*.mp3
from<CDN_URL>/<path>
. - Logs success (✅) or failure (❌) per file and prints a summary.
Examples
# Download two UI sounds into default folder
npx react-sounds-cli pick ui/click ui/hover
# Download notification sounds into a custom folder
npx react-sounds-cli pick notification/success notification/error --output=./assets/sfx
# Use a custom CDN endpoint
REACT_SOUNDS_CDN="https://my.cdn.endpoint" \
npx react-sounds-cli pick game/explosion
Configure React-Sounds to Use Local Files
In your app entry point, import setCDNUrl
and point it at your download folder:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { setCDNUrl } from 'react-sounds';
// If you downloaded to public/sounds:
setCDNUrl('./public/sounds');
// Or your custom folder:
setCDNUrl('./assets/sfx');
ReactDOM.render(<App />, document.getElementById('root'));
Tips
- Run
npx react-sounds-cli list
first to see available keys. - Invalid keys are skipped with an error log.
- Directories are created recursively—no manual setup required.
Customising the Sound Library
Enable your team to add new audio assets, keep TypeScript definitions in sync, and publish files to a private CDN.
1. Adding Custom Sounds & Generating the Manifest
Place your .mp3
files under sounds/{category}/
. Categories group sounds (e.g. ambient
, ui
, game
).
Install prerequisites
• Node.js
• Optional for accurate durations:ffprobe
(brew install ffmpeg
orsudo apt install ffmpeg
)Add an npm script to
package.json
{ "scripts": { "generate-manifest": "node scripts/generate-manifest.js" } }
Run the generator
npm run generate-manifest
This scans
sounds/
, hashes filenames, extracts durations, and writessrc/manifest.json
:{ "version": "1.0.0", "sounds": { "ambient/rain": { "src": "ambient/rain.a1b2c3d.mp3", "duration": 12.34 }, "ui/click": { "src": "ui/click.e4f5g6h.mp3", "duration": 0.85 } /* … */ } }
Commit both your new
.mp3
files and the updatedsrc/manifest.json
.
Programmatic Access Example
import manifest from "./manifest.json";
export function playSound(id) {
const entry = manifest.sounds[id];
if (!entry) throw new Error(`Sound "${id}" not found`);
const url = `/cdn/sounds/${entry.src}`;
const audio = new Audio(url);
return audio.play();
}
// Usage
playSound("notification/success");
2. Customising Type Safety
Keep sound IDs type-safe in your code by regenerating TypeScript definitions.
Add npm script
{ "scripts": { "generate-sound-types": "node scripts/generate-types.js" } }
Run to emit
src/types.ts
npm run generate-sound-types
It reads
src/manifest.json
and produces:export type SoundCategory = 'ambient' | 'ui' | 'game' | /*…*/; export type AmbientSoundName = 'rain' | /*…*/; export type UiSoundName = 'click' | /*…*/; export type LibrarySoundName = | `ambient/${AmbientSoundName}` | `ui/${UiSoundName}` | /*…*/; export interface SoundOptions { volume?: number; rate?: number; loop?: boolean; } export interface SoundHookReturn { play(options?: SoundOptions): Promise<void>; stop(): void; pause(): void; resume(): void; isPlaying: boolean; isLoaded: boolean; }
Integrate in your build
• Add"prebuild": "npm run generate-sound-types"
to ensure definitions stay fresh.
• Import types:import { LibrarySoundName, SoundOptions } from "./types"; function useSound(name: LibrarySoundName): SoundHookReturn { … }
3. Hosting Files on a Private CDN
Upload generated assets to DigitalOcean Spaces (or any S3-compatible storage).
Configure environment variables in
.env
:DO_SPACES_KEY=your_access_key DO_SPACES_SECRET=your_secret DO_SPACES_ENDPOINT=nyc3.digitaloceanspaces.com DO_SPACES_NAME=your-space-name CDN_PATH=private/v1 # optional prefix
Add npm script:
{ "scripts": { "upload-sounds": "node scripts/upload-to-cdn.js" } }
Upload assets: • Dry run (preserves existing files):
npm run upload-sounds
• Full sync (delete before upload):
npm run upload-sounds -- --delete
What the Upload Script Does
- Validates
src/manifest.json
presence - Reads env vars above
- If
--delete
is passed, clearsCDN_PATH/
prefix - Iterates
manifest.sounds
, mapping localsounds/{category}/{name}.mp3
→ CDN key${CDN_PATH}/${src}
- Uploads each with
ContentType: audio/mpeg
andACL: public-read
- Logs a summary and exits with code 1 on errors
Following these steps ensures your audio assets, metadata, and types remain consistent and publish-ready on your private CDN.
Development & Contribution
This guide covers local setup, build/release workflows and updating the demo website for react-sounds.
Prerequisites
- Node.js v14+ and npm
- Git
- AWS credentials (for sound uploads) in a
.env
at project root:AWS_S3_BUCKET=your-bucket-name AWS_ACCESS_KEY_ID=AKIA… AWS_SECRET_ACCESS_KEY=…
Local Setup
- Clone the repo and install dependencies
git clone https://github.com/aediliclabs/react-sounds.git cd react-sounds npm install
- Verify TypeScript and bundling
npm run build
Build Scripts Configuration
package.json (scripts excerpt)
"scripts": {
"build": "rm -rf dist/ && rollup -c && cp src/manifest.json dist/",
"generate-manifest": "node scripts/generate-manifest.js",
"generate-types": "node scripts/generate-types.js",
"upload-to-cdn": "node scripts/upload-to-cdn.js",
"build:sounds": "npm run generate-manifest && npm run generate-types && npm run upload-to-cdn",
"build:all": "npm run build:sounds && npm run build"
}
What each script does
- build
• Cleansdist/
• Bundles CJS + ESM via Rollup
• Copiessrc/manifest.json
intodist/
- generate-manifest
• Scanssrc/sounds/*.wav
• Writesmanifest.json
with filenames & checksums - generate-types
• Readsmanifest.json
• Emitsdist/manifest.d.ts
for typed imports - upload-to-cdn
• Uses@aws-sdk/client-s3
• Uploads sound files +manifest.json
to S3 - build:sounds
• Chains manifest → types → CDN upload - build:all
• Publishes sounds (build:sounds)
• Bundles the library (build)
Rollup Configuration for Bundling
Rollup outputs both CommonJS and ES modules with source maps. Externalizes React, ReactDOM and Howler.
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
import typescript from "@rollup/plugin-typescript";
import { terser } from "rollup-plugin-terser";
const pkg = require("./package.json");
export default {
input: "src/index.ts",
output: [
{ file: pkg.main, format: "cjs", sourcemap: true, exports: "named" },
{ file: pkg.module, format: "esm", sourcemap: true, exports: "named" }
],
external: ["react", "react-dom", "howler"],
plugins: [
resolve(),
commonjs(),
json(),
typescript({
tsconfig: "./tsconfig.json",
exclude: ["**/*.test.ts", "**/*.test.tsx"]
}),
terser()
]
};
Usage:
npm run build
to generatedist/
- Omit
terser()
for debug builds - To bundle additional asset types, update
json()
or add asset-specific plugins
TypeScript Configuration Highlights
{
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"declaration": true,
"sourceMap": true,
"resolveJsonModule": true,
"jsx": "react",
"target": "es2018",
"module": "esnext",
"strict": true
},
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts*"]
}
declaration
: Emits.d.ts
alongside.js
resolveJsonModule
: Allowsimport manifest from './manifest.json'
jsx: "react"
: Compiles TSX React components
Publishing a New Version
- Bump version and tag
npm version [patch|minor|major]
- Build sounds + library
npm run build:all
- Publish to npm
npm publish --access public
- Push code and tags
git push origin main --tags
Updating the Demo Website
Demo scripts live in package.json
under start:site
, build:site
and deploy:site
. To work on the demo:
- Install dependencies (if a subfolder,
cd demo && npm install
) - Preview locally
npm run start:site
- Build static assets
npm run build:site
- Deploy (GitHub Pages, Netlify, etc.)
npm run deploy:site
Ensure you update component imports as you add new sounds or props; the demo auto–reloads on changes.