Core

Overview

Project overview, philosophy, architecture layers, and non-goals of sensory-ui.

sensory-ui is a semantic, modular, opt-in sound layer for React and Next.js apps, distributed as a shadcn/ui registry package. It adds meaningful, non-intrusive sound feedback to UI interactions. It is not a sound effects library, not a music player, not a game audio engine.

Target platform: Next.js (App Router and Pages Router) + React Distribution: shadcn/ui registry (CLI installable) Runtime footprint: Minimal. Built-in audio is synthesized programmatically via the Web Audio API. No public/ assets, no base64 blobs for built-in packs. No global side effects, no forced re-renders.


Core Philosophy

PrincipleWhat It Means in Practice
Semantic, not decorativeEvery sound maps to a meaningful interaction type (activation, navigation, system feedback). No random audio.
Explicit opt-in onlyNo component ever makes a sound unless you pass a sound prop to it. Nothing is automatic.
Accessibility-firstRespects prefers-reduced-motion, provides a global kill-switch, and never forces audio on users.
Minimal runtime footprintZero npm dependencies. The engine (~2 KB gzipped) and full library (~28 KB gzipped) use only native Web Audio API.
Registry-nativeBuilt specifically for the shadcn/ui distribution model. One install command, then it lives inside your project like any other component.
ComposableWorks with any combination of Radix primitives, any shadcn/ui component, or raw HTML elements via the usePlaySound hook.

Architecture Layers

sensory-ui is composed of four runtime layers. Sound travels one path: a user event becomes a command, the command passes policy, resolves to a handler, and the engine plays the effect:

Architecture states
LayerFileResponsibility
Providerconfig/provider.tsxReact context, playSound dispatch, reduced-motion check, AudioContext lifecycle
Componentbutton.tsx, dialog.tsx, etc.Accepts sound prop, calls playSound on interaction events
Engineconfig/engine.tsWeb Audio API singleton, buffer decode/cache, playback scheduling
Configconfig/config.ts, config/registry.tsRole resolution, pack lookup, volume, enable/disable

The distribution layer (shadcn registry) is separate from runtime and handles how files are installed into your project.


File Structure After Installation

Everything lives inside a single well-scoped folder:

components/ui/sensory-ui/
  config/                 ← internal engine & config layer
    engine.ts             ← Web Audio engine (core)
    provider.tsx          ← SensoryUIProvider (React context)
    config.ts             ← runtime config + mergeConfig + resolveRole
    sound-roles.ts        ← TypeScript types for all SoundRole values
    registry.ts           ← role → SoundPack mapping
    use-play-sound.ts     ← usePlaySound({ sound, volume }) hook
  sounds/                 ← audio synthesizers as TypeScript modules
    core/                 ← instrument-based sound generation
      tunes.ts            ← base tune definitions (frequencies, durations)
      instruments.ts      ← 9 instrument configurations
      factory.ts          ← tune + instrument → synthesizer function
      effects.ts          ← reverb, delay, chorus, distortion chain
      pack-generator.ts   ← generates full packs from instruments
      index.ts            ← re-exports core modules
    packs.ts              ← all 9 sound packs
    index.ts              ← main entry point for sounds
  button.tsx              ← patched shadcn Button with sound prop
  dialog.tsx
  tabs.tsx
  ...

Built-in sounds are synthesized programmatically via the Web Audio API. No files in public/sounds/, no base64 blobs, no network requests for built-in packs. Custom role overrides may load audio from URLs or base64 data URIs.


Quick Example

// app/layout.tsx
import { SensoryUIProvider } from "@/components/ui/sensory-ui/config/provider";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <SensoryUIProvider>{children}</SensoryUIProvider>
      </body>
    </html>
  );
}
// Any component
import { Button } from "@/components/ui/sensory-ui/button";

export function SaveButton() {
  return (
    <Button sound="interaction.tap" onClick={handleSave}>
      Save
    </Button>
  );
}

Non-Goals (v1)

These are explicitly out of scope for the initial version:

  • Auto-attaching sounds to components without a sound prop
  • Background music or ambient audio layers
  • Game-style soundscapes or layered audio
  • Heavy Web Audio DSP effects
  • Sound-triggered React re-renders
  • Audio preloading on mount