logo

Nurav UI

Community
Hooks

useSound

A specialized hook for zero-configuration, audible UI feedback using performance-optimized Base64 audio caching.Copy Markdown

Overview

useSound is a performance-first hook designed to provide immediate auditory feedback for UI interactions like button clicks and theme toggles. Unlike standard audio implementations that fetch files over the network, useSound uses Base64 embedded data and a global singleton cache to ensure near-zero latency playback.

Key Features:

  • Zero Configuration: No need to host .mp3 files in your public folder; sounds are embedded directly in the code.
  • Global Cache: Reuses Audio objects across your entire application to minimize memory usage and initialization overhead.
  • Persistent Mute: Automatically respects and syncs a global mute state via localStorage.
  • SSR Safe: Safely handles server-side rendering environments.

Installation

Automatic (CLI)

Terminal
npx shadcn@latest add @nurav-ui/use-sound

Alternatively, install via direct URL:

Terminal
npx shadcn@latest add https://nurav-ui.vercel.app/r/use-sound.json

Manual Setup

Create the audio data file

Create hooks/sounds-data.ts and paste your Base64 encoded audio strings:

hooks/sounds-data.ts
export const ON_SOUND = 'SUQzBAAA...'; // Full Base64 string here
export const OFF_SOUND = 'SUQzBAAA...'; // Full Base64 string here
export const CLICK_SOUND = 'SUQzBAAA...'; // Full Base64 string here

Create the hook file

Copy the source into hooks/use-sound.ts:

hooks/use-sound.ts
"use client";

import { useCallback, useEffect, useState } from "react";
import { ON_SOUND, OFF_SOUND, CLICK_SOUND } from "./sounds-data";

const audioCache: Record<string, HTMLAudioElement> = {};

export function useSound() {
  const [isMuted, setIsMuted] = useState(false);

  useEffect(() => {
    if (typeof window !== "undefined") {
      setIsMuted(localStorage.getItem("nurav-ui-mute") === "true");
    }
  }, []);

  const toggleMute = useCallback(() => {
    setIsMuted((prev) => {
      const next = !prev;
      localStorage.setItem("nurav-ui-mute", String(next));
      return next;
    });
  }, []);

  const play = useCallback((base64Data: string) => {
    if (isMuted || typeof window === "undefined") return;

    if (!audioCache[base64Data]) {
      audioCache[base64Data] = new Audio(`data:audio/mp3;base64,${base64Data}`);
      audioCache[base64Data].load();
    }

    const audio = audioCache[base64Data];
    audio.currentTime = 0;
    audio.play().catch(() => {});
  }, [isMuted]);

  return {
    clickOn: useCallback(() => play(ON_SOUND), [play]),
    clickOff: useCallback(() => play(OFF_SOUND), [play]),
    mouseClick: useCallback(() => play(CLICK_SOUND), [play]),
    isMuted,
    toggleMute,
  };
}

Usage

Basic Button Click

Providing audible feedback for a standard button interaction.

import { useSound } from '@/hooks/use-sound';

export default function ClickDemo() {
  const { mouseClick } = useSound();

  return (
    <button onClick={mouseClick} className="p-4 bg-primary text-white rounded">
      Click Me
    </button>
  );
}

Integrated Global Mute

Using the hook to manage a global sound settings toggle.

import { useSound } from '@/hooks/use-sound';
import { Volume2, VolumeX } from 'lucide-react';

export default function SoundToggle() {
  const { isMuted, toggleMute } = useSound();

  return (
    <button onClick={toggleMute} className="flex gap-2 items-center">
      {isMuted ? <VolumeX /> : <Volume2 />}
      <span>{isMuted ? 'Unmute Sounds' : 'Mute Sounds'}</span>
    </button>
  );
}

Controlled by SoundToggle

The recommended way to provide users with control over these sounds is by using the SoundToggle component. This component automatically stays in sync with all elements using the useSound hook.

Sound Toggle Example

Fully Customizable: Since the component is downloaded as raw source code into your project, you can easily edit its styling, animations, or even add custom sound logic to fit your specific design language.

Installation

Terminal
npx shadcn@latest add @nurav-ui/sound-toggle

Usage

To ensure the toggle is accessible globally, place it at the highest level of your component tree.

Place it in your root layout so it persists across all routes:

app/layout.tsx
import { SoundToggle } from "@/components/nurav-ui/SoundToggle";

export default function Layout({ children }) {
  return (
    <html>
      <body>
        {children}
        <SoundToggle />
      </body>
    </html>
  );
}

Efficiency & Architecture

How it works

  1. Embedding: Audio files are converted to Base64 strings. This embeds the assets directly into your Javascript bundle, making the hook completely self-contained and eliminating network requests during playback.
  2. Standard Audio Objects: The hook uses the browser's native Audio API, which is broadly supported and handles hardware acceleration for audio decoding.
  3. Singleton Caching: A global audioCache object sits outside the hook. This ensures that no matter how many components call useSound, only one instance of each audio asset is ever loaded into memory.

Performance Tip

Network vs Bundle Size: While Base64 increases your bundle size slightly (approx 30%), it eliminates the "pop" or delay caused by network latency when a sound is first triggered. For UI feedback sounds under 50KB, this is the most efficient way to achieve a premium, responsive feel.


API Reference

Return Value

Returns an object with following properties:

PropTypeDefaultDescription
clickOn
() => voidPlays the high-pitched "on" sound effect.
clickOff
() => voidPlays the low-pitched "off" sound effect.
mouseClick
() => voidPlays the standard UI tick/click sound effect.
isMuted
booleanCurrent global mute state.
toggleMute
() => voidToggles the global mute state and persists it to `localStorage`.

Built by Varun

Last Updated:April 11, 2026

On this page