Chat about this codebase

AI-powered code exploration

Online

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

  1. Reads dist/manifest.json to resolve each key’s CDN path.
  2. Creates the output directory and subfolders (e.g. ui/, notification/, game/).
  3. Streams each *.mp3 from <CDN_URL>/<path>.
  4. 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).

  1. Install prerequisites
    • Node.js
    • Optional for accurate durations: ffprobe (brew install ffmpeg or sudo apt install ffmpeg)

  2. Add an npm script to package.json

    {
      "scripts": {
        "generate-manifest": "node scripts/generate-manifest.js"
      }
    }
    
  3. Run the generator

    npm run generate-manifest
    

    This scans sounds/, hashes filenames, extracts durations, and writes src/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 }
        /* … */
      }
    }
    
  4. Commit both your new .mp3 files and the updated src/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.

  1. Add npm script

    {
      "scripts": {
        "generate-sound-types": "node scripts/generate-types.js"
      }
    }
    
  2. 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;
    }
    
  3. 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).

  1. 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
    
  2. Add npm script:

    {
      "scripts": {
        "upload-sounds": "node scripts/upload-to-cdn.js"
      }
    }
    
  3. 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, clears CDN_PATH/ prefix
  • Iterates manifest.sounds, mapping local sounds/{category}/{name}.mp3 → CDN key ${CDN_PATH}/${src}
  • Uploads each with ContentType: audio/mpeg and ACL: 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

  1. Clone the repo and install dependencies
    git clone https://github.com/aediliclabs/react-sounds.git
    cd react-sounds
    npm install
    
  2. 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
    • Cleans dist/
    • Bundles CJS + ESM via Rollup
    • Copies src/manifest.json into dist/
  • generate-manifest
    • Scans src/sounds/*.wav
    • Writes manifest.json with filenames & checksums
  • generate-types
    • Reads manifest.json
    • Emits dist/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 generate dist/
  • 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: Allows import manifest from './manifest.json'
  • jsx: "react": Compiles TSX React components

Publishing a New Version

  1. Bump version and tag
    npm version [patch|minor|major]
    
  2. Build sounds + library
    npm run build:all
    
  3. Publish to npm
    npm publish --access public
    
  4. 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:

  1. Install dependencies (if a subfolder, cd demo && npm install)
  2. Preview locally
    npm run start:site
    
  3. Build static assets
    npm run build:site
    
  4. 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.