# Get Started --- title: Get Started description: A quick guide to adding audio/ui components to your application. --- This guide walks you through the essential steps to add audio/ui to your shadcn project. ## Prerequisites audio/ui is built on **React 19** and [Tailwind CSS v4](https://tailwindcss.com). Ensure your project is set up with these technologies before proceeding. We recommend following the [Official shadcn/ui Installation Guide](https://ui.shadcn.com/docs/installation) to prepare your environment. ## Setup the audio/ui Registry Add the audio/ui registry namespace to your `components.json` and set your component library and style. Learn more about registry config from [shadcn registry docs](https://ui.shadcn.com/docs/registry). ```json { "registries": { "@audio": "https://audio-ui.xyz/r/{style}/{name}.json" } } ``` ## Components You can install audio/ui using the [**shadcn CLI**](https://ui.shadcn.com/docs/cli) for automation or by manual installation for full control. Both methods apply to everything in [Docs](/docs/) and the [component catalog](/components), including the `block-*` blocks and demos you install with `@audio/…`. ## Styling Components are styled with a design token system defined by CSS variables and implemented with Tailwind CSS, following the same conventions as shadcn/ui so they're fully customizable. See the [Styling guide](/docs/styling) for details. ## AI-Enhanced Workflow audio/ui is designed from the ground up to be **AI-friendly**, making it easier for language models to understand and modify your UI. - **llms.txt**: Access our [llms.txt](/llms.txt) file for a structured map of our documentation and architecture optimized for AI agents. - **Copy Markdown**: Every page includes a **Copy Markdown** feature, allowing you to instantly feed documentation or code into your AI-driven development workflows. ## Next Steps Head over to the [Audio Player](/docs/components/base/player) docs to install your first component, or browse the [component catalog](/components) for ready-to-use blocks. # Introduction --- title: Introduction description: A set of accessible and composable Audio UI components. Built on top of shadcn/ui, designed to be copied, pasted, and owned. --- **audio/ui** is a set of accessible and composable Audio UI components. Built on top of [shadcn/ui](https://ui.shadcn.com/), it's designed for you to copy, paste, and own. **This is not a component library. It is how you build your audio component library.** You know how most traditional component libraries work: you install a package from NPM, import the components, and use them in your app. This works well until you need to customize a component to fit your design system, or need one that isn't included. Often you end up wrapping library components, overriding styles, or mixing components from different libraries with incompatible APIs. ## Open Code audio/ui hands you the actual component code. You have full control to customize and extend the components to your needs: - **Full Transparency:** You see exactly how each component is built. - **Easy Customization:** Modify any part of a component to fit your design and functionality requirements. - **AI Integration:** Access to the code makes it straightforward for LLMs to read, understand, and even improve your components. ## Composition Every component in audio/ui shares a common, composable interface. Browse the [component catalog](/components) for composed examples by category, or install primitives and blocks straight from the registry — copy-ready layouts and lower-level pieces so you can pick the right level of abstraction for each screen. ## Beautiful Defaults audio/ui comes with a collection of components that have carefully chosen default styles, designed to look good on their own and to work well together as a consistent system. ## AI-Ready The design of audio/ui makes it easy for AI tools to work with your code. Its open code and consistent API allow AI models to read, understand, and even generate new components. ## Open Source This project is open source. We welcome contributions, feedback, or improvements. Check out our [repository](https://github.com/ouestlabs/audio-ui) on GitHub. ## Next Steps Ready to get started? Head over to the [Get started](/docs/get-started) guide to add audio/ui to your project. # MCP --- title: MCP description: Learn how to use the shadcn/ui MCP (Model Context Protocol) with audio/ui. --- MCP is an open protocol that standardizes how applications provide context to LLMs. ## Configure MCP Run the following command to configure the mcp server: ```bash npx shadcn@latest mcp init ``` Select your MCP client as prompted, then enable the MCP server in your client to finish setup. ## Usage You can now ask your IDE to use any audio/ui component. Here are some prompt examples: - Build an audio player with queue management, shuffle, and repeat modes using the Audio Player. - Create a synth control panel with Knob, XY Pad, and Channel Strip components. - Add a mixer-style channel strip with Fader and Transport controls for a DAW-like interface. # Registry --- title: Registry description: Learn how to use the audio/ui Registry with shadcn/ui. --- The audio/ui Registry gives you instant access to production-ready audio components. Add it once, then pull in flexible, composable blocks on demand. ## Setup the audio/ui Registry Add the audio/ui registry namespace to your `components.json`. Learn more about registry config from [shadcn registry docs](https://ui.shadcn.com/docs/registry). ```json { "registries": { "@audio": "https://audio-ui.xyz/r/{style}/{name}.json" } } ``` ## Usage Install blocks via the shadcn CLI using the `@audio/{name}` syntax. ```bash npx shadcn@latest add @audio/block-pocket-synth ``` Install components via the shadcn CLI using the `@audio/{name}` syntax. ```bash npx shadcn@latest add @audio/knob ``` # Roadmap --- title: Roadmap description: Current state of audio/ui and where we're headed next. --- **audio/ui** is a component library for building audio interfaces in React applications. Built on top of [shadcn/ui](https://ui.shadcn.com/), we provide accessible, composable components that you can copy, paste, and customize to fit your needs. ## Current Status We're actively building and improving `audio/ui`. Here's what's available today: ### Components - **Audio Player**: A fully-featured, composable audio player with queue management, shuffle, repeat modes, and volume control - **Controls**: Knob, Fader, XY Pad, and Transport primitives for building custom audio interfaces - **Channel Strip**: Layout patterns for mixer-style columns combining controls, labels, and readouts ### Library - **Audio Store**: A Zustand-based state management solution for audio playback with localStorage persistence - **HTML Audio**: Core singleton for loading and controlling HTML5 audio with retry logic and event handling ### Blocks - **Pre-assembled Patterns**: Ready-to-use audio player and channel strip patterns that combine multiple components — browse the [component catalog](/components) ## What's Next - **More Audio Components**: Additional audio-specific UI patterns and controls - **Enhanced Player Features**: Advanced playback controls, playlist management, and audio visualization - **Visualization Components**: Waveform displays, spectrum analyzers, and other audio visualizations - **Radix UI Support**: A Radix-based variant of the component set, alongside the current Base UI implementation ## Contributing We're building this in the open and welcome contributions! Whether it's bug reports, feature requests, or code contributions, we'd love to have you involved. Check out our [GitHub repository](https://github.com/ouestlabs/audio-ui) to get started. # Styling --- title: Styling description: A guide for extended styling options with our color system. --- ## Overview audio/ui extends the official shadcn/ui theming system with additional semantic tokens that enable precise, context-aware UI states. While you can use standard shadcn/ui themes, we recommend adopting these custom color tokens to ensure consistent contrast across component states such as alerts and badges. ## Extended Tokens To support advanced state communication, we extend the base palette with additional semantic tokens: - `--destructive-foreground`: Foreground color specifically for destructive actions. - `--info` & `--info-foreground`: Background and text colors for informational states. - `--success` & `--success-foreground`: Background and text colors for success/positive outcomes. - `--warning` & `--warning-foreground`: Background and text colors for cautionary states. - `--invert` & `--invert-foreground`: Background and text colors for invert states. ```css title="app/globals.css" @theme inline { --color-destructive-foreground: var(--destructive-foreground); --color-info: var(--info); --color-info-foreground: var(--info-foreground); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-invert: var(--invert); --color-invert-foreground: var(--invert-foreground); } :root { --destructive-foreground: var(--color-red-800); --info: var(--color-violet-500); --info-foreground: var(--color-violet-900); --success: var(--color-emerald-500); --success-foreground: var(--color-emerald-900); --warning: var(--color-yellow-500); --warning-foreground: var(--color-yellow-900); --invert: var(--color-zinc-900); --invert-foreground: var(--color-zinc-50); } .dark { --destructive-foreground: var(--color-red-600); --info: var(--color-violet-500); --info-foreground: var(--color-violet-600); --success: var(--color-emerald-500); --success-foreground: var(--color-emerald-600); --warning: var(--color-yellow-500); --warning-foreground: var(--color-yellow-600); --invert: var(--color-zinc-700); --invert-foreground: var(--color-zinc-50); } ``` For general customization guidelines such as adding your own custom colors or switching between utility classes and variables, please refer to the [official shadcn/ui documentation](https://ui.shadcn.com/docs/theming). # Audio Store --- title: Audio Store description: Zustand-based store and helper actions for queue and playback management. --- ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/store ``` Install the runtime dependencies: ```bash npm install zustand ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ### Import Import the store hook and types: ```tsx import { useAudioStore, calculateNextIndex, calculatePreviousIndex, canUseDOM, type AudioStore, type RepeatMode, type InsertMode, } from "@/lib/audio-store"; ``` ## Core Concepts ### useAudioStore Hook Performance Best Practices: Use granular selectors for better performance. Subscribe only to the specific slices of state you need. This prevents unnecessary re-renders when unrelated state changes. Access the store with granular selectors for better performance: ```tsx const currentTime = useAudioStore((s) => s.currentTime); const isPlaying = useAudioStore((s) => s.isPlaying); ``` For multiple values, extract them inside your component: ```tsx function PlayerStatus() { const currentTrack = useAudioStore((s) => s.currentTrack); const duration = useAudioStore((s) => s.duration); const isPlaying = useAudioStore((s) => s.isPlaying); return (

{currentTrack?.title} ({duration}s) - {isPlaying ? "▶" : "⏸"}

); } ``` ### Architecture ```mermaid graph TB A(React Components) -->|useAudioStore selector| B(AudioStore Zustand) B -->|State Updates| A B -->|Actions| C($audio Singleton) C -->|HTMLAudioElement| D(Browser Audio API) B -->|Persist| E(localStorage) E -->|Restore| B ``` ## Types ### Track Common audio track object. See [HTML Audio](/docs/lib/html-audio#track) for full definition. ### AudioStore State The store exposes state organized by concern: | Category | Property | Type | Description | | ----------------- | -------------------- | -------------------------------- | ------------------------------- | | **Playback** | `isPlaying` | `boolean` | Currently playing | | | `isLoading` | `boolean` | Loading track | | | `isBuffering` | `boolean` | Buffering audio | | | `isError` | `boolean` | Error state | | | `errorMessage` | `string \| null` | Error details | | **Current Track** | `currentTrack` | `Track \| null` | Active track object | | | `currentTime` | `number` | Playback position (seconds) | | | `duration` | `number` | Track length | | | `progress` | `number` | Normalized progress 0–1 | | | `bufferedTime` | `number` | Buffered amount | | **Queue** | `queue` | `Track[]` | Array of tracks | | | `currentQueueIndex` | `number` | Active track index | | **Controls** | `volume` | `number` | Volume 0–1 | | | `isMuted` | `boolean` | Mute state | | | `repeatMode` | `"none" \| "one" \| "all"` | Repeat mode | | | `shuffleEnabled` | `boolean` | Shuffle state | | | `insertMode` | `"first" \| "last" \| "after"` | Insertion position for queue | ### Types | Type | Values | Description | | ------------ | -------------------------------- | ------------------------------------------ | | `RepeatMode` | `"none" \| "one" \| "all"` | Repeat playback mode | | `InsertMode` | `"first" \| "last" \| "after"` | Where new tracks are added to the queue | #### RepeatMode Flow ```mermaid stateDiagram-v2 [*] --> none none --> one: changeRepeatMode() one --> all: changeRepeatMode() all --> none: changeRepeatMode() note right of none No repeat Play queue once end note note right of one Repeat current track indefinitely end note note right of all Repeat entire queue Loop forever end note ``` ## Utility Functions ### calculateNextIndex(queue, currentIndex, shuffleEnabled, repeatMode) Calculate the next track index based on playback mode. ```tsx const nextIndex = calculateNextIndex( queue, currentQueueIndex, shuffleEnabled, repeatMode ); // Returns: number (track index or -1 if none) ``` ### calculatePreviousIndex(queue, currentIndex, shuffleEnabled, repeatMode) Calculate the previous track index based on playback mode. ```tsx const prevIndex = calculatePreviousIndex( queue, currentQueueIndex, shuffleEnabled, repeatMode ); // Returns: number (track index or -1 if none) ``` ### canUseDOM() Check if code runs in a DOM environment (not SSR). ```tsx if (canUseDOM()) { // Browser-only code } ``` SSR Considerations: The store uses canUseDOM(){" "} internally to handle server-side rendering. When accessing the store in SSR contexts, ensure you check for DOM availability before calling store methods that interact with browser APIs. ## Actions Access actions via selectors: `const action = useAudioStore((s) => s.actionName)` | Category | Action | Signature | Description | | -------------- | ------------------- | ----------------------------------------------------------- | ---------------------------------------------------------- | | **Playback** | `play` | `() => Promise` | Start or resume playback | | | `pause` | `() => void` | Pause playback | | | `togglePlay` | `() => void` | Toggle play/pause state | | | `seek` | `(time: number) => void` | Seek to position (seconds) | | **Navigation** | `next` | `() => Promise` | Play next track | | | `previous` | `() => Promise` | Play previous track | | | `setCurrentTrack` | `(track: Track \| null) => Promise` | Load and play specific track | | | `setQueueAndPlay` | `(tracks: Track[], startIndex: number) => Promise` | Set queue and play from index | | **Queue** | `addToQueue` | `(track: Track, mode?: InsertMode) => void` | Add track to queue (supports "first", "last", "after") | | | `removeFromQueue` | `(trackId: string) => void` | Remove track from queue | | | `moveInQueue` | `(fromIndex: number, toIndex: number) => void` | Move track in queue | | | `setQueue` | `(tracks: Track[], startIndex?: number) => void` | Replace entire queue | | | `clearQueue` | `() => void` | Clear all tracks from queue | | **Volume** | `setVolume` | `(volume: number) => void` | Set volume (0-1) | | | `toggleMute` | `() => void` | Toggle mute state | | **Modes** | `changeRepeatMode` | `() => void` | Cycle repeat mode (none → one → all → none) | | | `setRepeatMode` | `(mode: RepeatMode) => void` | Set repeat mode | | | `shuffle` | `() => void` | Randomize queue order | | | `unshuffle` | `() => void` | Restore original queue order | | | `setInsertMode` | `(mode: InsertMode) => void` | Set insert mode | | **Error** | `setError` | `(message: string \| null) => void` | Set or clear error state | ## Examples ### Basic Usage ```tsx import { useAudioStore } from "@/lib/audio-store"; import { formatDuration } from "@/lib/audio"; function PlayerStatus() { // Access state with selectors const currentTrack = useAudioStore((s) => s.currentTrack); const currentTime = useAudioStore((s) => s.currentTime); const duration = useAudioStore((s) => s.duration); const isPlaying = useAudioStore((s) => s.isPlaying); // Access actions const togglePlay = useAudioStore((s) => s.togglePlay); const next = useAudioStore((s) => s.next); const previous = useAudioStore((s) => s.previous); return (

Track: {currentTrack?.title ?? "None"}

Time: {formatDuration(currentTime)} / {formatDuration(duration)}

Status: {isPlaying ? "▶" : "⏸"}

); } ``` ### Queue Management ```tsx function QueueManager() { const queue = useAudioStore((s) => s.queue); const addToQueue = useAudioStore((s) => s.addToQueue); const removeFromQueue = useAudioStore((s) => s.removeFromQueue); const clearQueue = useAudioStore((s) => s.clearQueue); return (
{queue.map((track) => (
{track.title}
))}
); } ``` ### Direct Store Access ```tsx import { useAudioStore } from "@/lib/audio-store"; // Read state without React subscriptions const state = useAudioStore.getState(); console.log(state.isPlaying); // Call actions directly await useAudioStore.getState().play(); // Subscribe to changes (returns unsubscribe function) const unsubscribe = useAudioStore.subscribe( (s) => s.isPlaying, (isPlaying) => console.log("Playing:", isPlaying) ); ``` ## Persistence The store automatically persists a subset of state to localStorage using Zustand's `persist` middleware: | Category | Properties | | ------------ | --------------------------------------------------------------------- | | **Playback** | `currentTrack`, `currentTime`, `currentQueueIndex` | | **Queue** | `queue` | | **Settings** | `volume`, `isMuted`, `repeatMode`, `shuffleEnabled`, `insertMode` | **Storage Key:** `audio:ui:store` This allows users to resume playback and maintain queue state across page refreshes. ## Related - [HTML Audio](/docs/lib/html-audio) - Core audio playback singleton - [Audio Player](/docs/components/base/player) - Player UI components ## Notes Best Practices: - Prefer selector subscriptions for performance (subscribe to specific slices of state) - Async actions (play, next,{" "} setCurrentTrack) wait for audio loading - addToQueue supports "first", "last", and "after" insert modes - Queue shuffling randomizes order but preserves track identity - setQueueAndPlay and similar actions trigger audio loading via the [HTML Audio](/docs/lib/html-audio) - Persistence is automatic; no manual save required - Direct store access via getState() is useful for non-React code or imperative operations # HTML Audio --- title: HTML Audio description: Core audio helper and singleton for loading and controlling HTML5 audio. --- The `$htmlAudio` singleton manages playback of HTML5 audio with automatic retry logic, event handling, and volume fading. Use it alongside the [Audio Store](/docs/lib/audio-store) for full player functionality. ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/html ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ### Import Import the singleton and helpers from the audio library: ```tsx import { $htmlAudio, formatDuration, type Track } from "@/lib/html-audio"; ``` Or use the React hook for better integration: ```tsx import { useAudio } from "@/hooks/use-audio"; function MyComponent() { const { htmlAudio } = useAudio(); // Use htmlAudio instead of $audio } ``` ## Core API ### $htmlAudio (Singleton) Manages the underlying `HTMLAudioElement`, playback state, retries, and events. Initialize on client start - the instance is server-safe. ```tsx import { $htmlAudio } from "@/lib/html-audio"; // Initialize on the client $htmlAudio.init(); // Load and play await $htmlAudio.load("https://example.com/audio.mp3", 0); await $htmlAudio.play(); ``` Client initialization: The $htmlAudio singleton must be initialized on the client. Call $htmlAudio.init() from a client component or inside useEffect() so the underlying{" "} HTMLAudioElement is created only in the browser environment. #### Lifecycle | Method | Description | | ----------- | --------------------------------------------------------- | | `init()` | Initialize on the client. Safe to call multiple times. | | `cleanup()` | Reset and release the audio element (pause, clear src). | ```mermaid sequenceDiagram participant App participant AudioLib participant HTMLAudio App->>AudioLib: init() AudioLib->>HTMLAudio: new Audio() AudioLib->>HTMLAudio: setupEventListeners() Note over AudioLib,HTMLAudio: Ready for playback App->>AudioLib: load(url, startTime) AudioLib->>HTMLAudio: src = url AudioLib->>HTMLAudio: load() HTMLAudio-->>AudioLib: loadedmetadata AudioLib-->>App: Promise resolved App->>AudioLib: play() AudioLib->>HTMLAudio: play() HTMLAudio-->>AudioLib: playing event AudioLib-->>App: Promise resolved App->>AudioLib: cleanup() AudioLib->>HTMLAudio: pause() AudioLib->>HTMLAudio: src = "" ``` #### Playback | Method | Description | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `load(url, startTime?, isLive?)` | Load an audio source and wait for readiness. Pass `isLive` for live streams (longer timeout, no seek). Returns `Promise`. | | `play()` | Start or resume playback. Returns promise that resolves when browser allows playback. | | `pause()` | Pause playback immediately. | | `seek(time)` | Seek when metadata is available. Ignored for live streams. | Browser autoplay restrictions: The play() call returns a promise which may be rejected by browser autoplay policies if there was no user gesture. Wrap calls to play() in a try/catch and provide a fallback UI so your app handles playback interruptions gracefully. Live streams: For live streams (when isLive is true) seeking is disabled and long timeouts are used. Live streams are best handled with a separate code-path because the playback semantics differ from on-demand audio files. #### Volume | Method | Description | | --------------------------------- | ------------------------------------------------------------------ | | `setVolume(volume, fadeTime?)` | Set or fade volume (0–1). If `fadeTime` > 0, animates smoothly. | | `getVolume()` | Return current volume (0–1). | | `setMuted(muted)` | Mute or unmute. Restores previous volume when unmuting. | #### State | Method | Description | | ------------------------ | ------------------------------------------------------------------ | | `getDuration()` | Return loaded source duration (seconds) or `0` if unavailable. | | `getCurrentTime()` | Return current playback position (seconds). | | `isPaused()` | Return boolean - is playback paused. | | `getBufferedRanges()` | Return underlying `TimeRanges` or `null`. | | `getSource()` | Return current source URL string. | | `getAudioElement()` | Return raw `HTMLAudioElement` or `null` on server. | #### Events The library emits custom events via an internal `EventTarget`: ```tsx $htmlAudio.addEventListener("bufferingStart", () => console.log("Buffering...")); $htmlAudio.addEventListener("bufferingEnd", () => console.log("Ready to play")); $htmlAudio.addEventListener("playbackStarted", () => console.log("Playing")); $htmlAudio.addEventListener("audioError", () => console.error("Error")); $htmlAudio.addEventListener("bufferUpdate", (e) => { if (e instanceof CustomEvent) console.log("Buffered:", e.detail.bufferedTime); }); ``` ## Utilities ### formatDuration(seconds) Format seconds to `MM:SS` string. Handles invalid input by returning `"0:00"`. ```tsx import { formatDuration } from "@/lib/html-audio"; formatDuration(125); // "2:05" formatDuration(3661); // "61:01" ``` ### isLive(duration) Check if a duration value indicates a live stream. This is a method on the `HtmlAudio` class. ```tsx import { useAudio } from "@/hooks/use-audio"; function MyComponent() { const { htmlAudio } = useAudio(); const duration = htmlAudio.getDuration(); if (htmlAudio.isLive(duration)) { // Handle live stream (no seeking) } } ``` Live Stream Detection: The isLive() method checks if a duration is NaN, Infinity, or{" "} -Infinity. A duration of 0 is not considered a live stream (it just means the duration hasn't loaded yet). ## Types ### Track Common audio track object with optional fields: | Prop | Type | Default | Description | | ------------------ | -------------------- | --------- | ----------------------------------- | | `id` | `string \| number` | - | Unique identifier for the track. | | `url` | `string` | - | URL of the audio file or stream. | | `title` | `string` | - | Track title. | | `artist` | `string` | - | Artist name. | | `artwork` | `string` | - | Album artwork URL. | | `images` | `string[]` | - | Array of image URLs. | | `duration` | `number` | - | Track duration in seconds. | | `album` | `string` | - | Album name. | | `genre` | `string` | - | Genre. | | `live` | `boolean` | - | Whether this is a live stream. | | `[key: string]` | `unknown` | - | Additional properties. | ## Examples ### Basic Playback ```tsx import { $htmlAudio } from "@/lib/html-audio"; $htmlAudio.init(); async function playTrack(url: string) { try { await $htmlAudio.load(url, 0); await $htmlAudio.play(); } catch (error) { console.error("Playback failed:", error); } } ``` ### Volume Management ```tsx import { $htmlAudio } from "@/lib/html-audio"; // Immediate change $htmlAudio.setVolume(0.5); // Smooth fade over 1 second $htmlAudio.setVolume(0.8, 1000); // Mute with memory $htmlAudio.setMuted(true); $htmlAudio.setMuted(false); // Restores previous volume ``` ### React Component ```tsx import { $htmlAudio, formatDuration } from "@/lib/html-audio"; import { useEffect, useState } from "react"; function TimeDisplay() { const [time, setTime] = useState(0); useEffect(() => { const updateTime = () => setTime($htmlAudio.getCurrentTime()); const interval = setInterval(updateTime, 100); return () => clearInterval(interval); }, []); return {formatDuration(time)}; } ``` ### Event Monitoring ```tsx import { $htmlAudio } from "@/lib/html-audio"; $htmlAudio.addEventListener("bufferingStart", () => { console.log("Loading audio..."); }); $htmlAudio.addEventListener("bufferingEnd", () => { console.log("Ready to play"); }); $htmlAudio.addEventListener("audioError", (event) => { console.error("Audio error:", event); }); ``` ## Related - [Audio Store](/docs/lib/audio-store) - Zustand store for queue and playback state management - [Audio Player](/docs/components/base/player) - Composable player UI components ## Notes - Singleton pattern: All methods access the same `$htmlAudio` instance - Server-safe: Methods check for client-side availability before executing - Automatic retries: Handles load/play errors with exponential backoff (max 3 attempts) - Volume fading: Animations use `requestAnimationFrame` for smooth transitions - Live streams: Disable seeking and use extended timeout for reliability - `formatDuration` handles edge cases (NaN, Infinity, negative values) # Channel Strip --- title: Channel Strip description: Layout shell for channel-style columns—header, sections with label, control, and readout, and footer—with vertical or horizontal orientation. component: true base: base --- Channel strip layout for grouping labels, controls, and value readouts in a mixer-style column. The registry component wraps `ChannelStrip` primitives from `@audio-ui/react` with card chrome, orientation-aware spacing, and header/footer layout presets. ## Overview - **Interaction model:** The strip wrapper does not handle pointer, wheel, or keyboard input; those behaviors come from the controls you render inside each `ChannelStripSection`. - **Layout:** Vertical (default) or horizontal `orientation`; optional `ChannelStripHeader` and `ChannelStripFooter`. With a footer, the styled root uses a responsive grid on wide screens so header and footer stay in distinct grid areas. - **Composition:** One `ChannelStripSection` per control line (label, control slot, readout). On a vertical strip, `ChannelStripContent` supports `layout="row"` so multiple sections sit side by side from the `md` breakpoint (flex row + wrap), and stack in one column on smaller viewports. - **Accessibility:** The root is a `section` with header/footer landmarks where used. `ChannelStripValue` is an `output` with `aria-live="polite"`. Name the strip with `aria-label` or `aria-labelledby`; wire controls with `id` / `aria-label` / `aria-labelledby` as usual. ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/channel-strip ``` Install the following dependencies: ```bash npm install @audio-ui/react ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ## Usage ```tsx import { ChannelStrip, ChannelStripContent, ChannelStripFooter, ChannelStripHeader, ChannelStripLabel, ChannelStripSection, ChannelStripValue, } from "@/components/channel-strip"; ``` **Vertical (default)** - header, one section, footer: ```tsx Channel 1 Level {/* your control */} Metadata ``` **Horizontal** - set `orientation` on the root: ```tsx Channel 1 {/* label, control, readout */} Metadata ``` **Sections in a row (vertical strip)** — responsive flex, not CSS Grid; narrow screens stack, from `md` up sections share a row and wrap if needed: ```tsx Channel 1 {/* A */} {/* B */} Metadata ``` ## Interaction ### Pointer | Behavior | Details | | -------- | ------- | | **Strip root** | Not an interactive target; pointer gestures are handled by child controls (`Fader`, `Knob`, etc.). | | **Sections** | Layout only; hit testing applies to controls you place inside each section. | ### Keyboard | Key | Action | | --- | ------ | | `Tab` / `Shift+Tab` | Moves focus among focusable elements inside the strip in DOM order. | The strip root is not focused as a control; there is no strip-level shortcut table beyond focus navigation. ### Wheel Wheel deltas are handled by whatever control currently has focus (if that control implements wheel), not by the strip wrapper. ## API reference `ChannelStrip` and its subcomponents accept `className` plus primitive props from the matching `ChannelStripPrimitive.*` typings in `@audio-ui/react`. ### Subcomponents | Export | Element | Role | | --- | --- | --- | | `ChannelStrip` | `section` | Root; orientation and card chrome. | | `ChannelStripHeader` | `header` | Optional title region. | | `ChannelStripContent` | `div` | Required wrapper for sections. | | `ChannelStripSection` | `div` | One label / control / value group. | | `ChannelStripLabel` | `span` | Section label. | | `ChannelStripValue` | `output` | Readout. | | `ChannelStripFooter` | `footer` | Optional footer region. | ### Root props | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Layout direction. | | `className` | `string` | - | Classes on the root `section`. | ### `ChannelStripContent` | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `layout` | `"stack" \| "row"` | `"stack"` | `stack`: sections in a column. `row` (vertical strip only in the styled registry): sections in a row from `md` with `flex-wrap`, stacked on small viewports. | | `className` | `string` | - | Classes on the content wrapper. | ### Section props | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `orientation` | `"vertical" \| "horizontal"` | inherited | Optional per-section layout override. | | `className` | `string` | - | Classes on the section wrapper. | Additional valid HTML attributes on each primitive part are forwarded where applicable. ## Examples ### Vertical (default) ### Horizontal ### Pan knob ### Pan / width ### XY filter pad # Fader --- title: Fader description: Linear slider control for gain and parameter adjustment with pointer, wheel, keyboard, orientation, and thumb mark variants. component: true base: base --- Linear fader for continuous parameters (volume, gain, send level, etc.). The registry component wraps `Fader` primitives from `@audio-ui/react` with size variants and thumb mark styling. ## Overview - **Interaction model:** Pointer drag on track or thumb, mouse wheel adjustment, keyboard nudging, and commit callbacks on release. - **Layout:** Vertical (default) or horizontal orientation, with `sm`, `default`, and `lg` size presets. - **Feedback:** Filled range + thumb with optional internal marks (`thumbMarks`). - **Accessibility:** The thumb is exposed as `role="slider"` with `aria-valuenow`, `aria-valuemin`, and `aria-valuemax`. ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/fader ``` Install the following dependencies: ```bash npm install @audio-ui/react class-variance-authority ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ## Usage ```tsx import { Fader } from "@/components/audio/fader"; ``` **Uncontrolled** - initial value only: ```tsx ``` **Controlled** - drive value from state: ```tsx const [gain, setGain] = useState(0); console.log("Committed:", value)} step={1} value={gain} /> ``` ## Interaction ### Pointer | Behavior | Details | | -------- | ------- | | **Track click / drag** | Clicking or dragging on the track updates the value from pointer position. | | **Thumb drag** | Dragging the thumb adjusts continuously, clamped to `min...max`. | | **Commit event** | `onValueCommit` fires when drag interaction ends. | ### Keyboard Focused thumb supports: | Key | Action | | --- | ------ | | `ArrowUp` / `ArrowRight` | Increase by `step` | | `ArrowDown` / `ArrowLeft` | Decrease by `step` | | `PageUp` | Increase by `step × 10` | | `PageDown` | Decrease by `step × 10` | | `Home` | Set to `min` | | `End` | Set to `max` | ### Wheel When focused, wheel input nudges the value by `step`. ## API reference `Fader` accepts style props (`size`, `thumbMarks`, `className`) plus primitive props from `FaderPrimitive.RootProps`. ### Value and state | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `value` | `number` | - | Controlled value. | | `defaultValue` | `number` | midpoint | Uncontrolled initial value. Midpoint is `(min + max) / 2`. | | `min` | `number` | `-60` | Minimum value. | | `max` | `number` | `6` | Maximum value. | | `step` | `number` | `1` | Quantization step for drag, wheel, and keyboard. | | `disabled` | `boolean` | `false` | Disables interaction. | | `onValueChange` | `(value: number) => void` | - | Live updates while adjusting. | | `onValueCommit` | `(value: number) => void` | - | Fires when interaction is committed. | | `aria-label` | `string` | - | Accessible name for the slider. | | `aria-labelledby` | `string` | - | ID of a labeling element. | ### Layout and visuals | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Direction of travel. | | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Track and thumb size preset. | | `thumbMarks` | `number \| false` | `3` | Number of marks in the thumb or `false` to hide them. | | `className` | `string` | - | Root wrapper class name. | Additional valid HTML attributes passed to `Fader.Root` are forwarded. ## Examples ### Vertical (default) ### Horizontal ### Size variants ### Thumb marks variants ### Channel strip #### Gain range in dB (`-60..+6`) #### Normalized range (`0..100`) #### Multi-channel strip # Knob --- title: Knob description: Rotary control for parameters - drag, wheel, keyboard, optional fader-style vertical pan, and reset via double-tap. component: true base: base --- Rotary knob for continuous parameters (level, cutoff, pan, etc.). The registry component wraps `Knob` primitives from `@audio-ui/react` with size variants and theme styles; behavior and geometry are documented below. ## Overview - **Interaction model:** Circular drag by default, optional vertical "fader" pan, mouse wheel, keyboard nudging, and **double-tap / double-click** to snap back to `defaultValue`. - **Feedback:** Track rail, highlighted arc between `anchor` and value when useful, and a needle aligned to the current value. - **Accessibility:** The draggable surface is a `role="slider"` with `aria-valuenow` / `min` / `max`; label via `aria-label` or `aria-labelledby`. ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/knob ``` Install the following dependencies: ```bash npm install @audio-ui/react class-variance-authority ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ## Usage ```tsx import { Knob } from "@/components/audio/knob"; ``` **Uncontrolled** - initial value only: ```tsx ``` **Controlled** - drive the value from state (typical in audio UIs): ```tsx const [cutoff, setCutoff] = useState(1000); ``` Pass **`defaultValue`** even when controlled if you use **double-tap reset**; it defines the value the knob jumps back to (still clamped and stepped). ## Interaction ### Pointer | Behavior | Details | | -------- | ------- | | **Circular drag (default)** | Value tracks accumulated rotation around the center from the value at **pointer down**. Uses `dragSensitivity` to choose the angular reference (see [Pointer mapping](#pointer-mapping)). | | **Vertical fader drag** | Set `dragOptions={{ verticalPanEnabled: true }}`. After a small movement threshold, a mostly-vertical drag nudges the value like a fader; circular drag still works when the gesture is more rotational. | | **Double-tap / double-click** | Two quick releases with **≤ ~12px** movement from each **pointer down**, within **~320ms**, resets to `defaultValue`, or to the midpoint of `min…max` if `defaultValue` is omitted. Fires `onValueCommit` like a normal commit. | Default geometry uses a large sweep (~288° from `angleGapRad`), so **`arc` and `revolution` can feel similar**; use a smaller `angleRange` if you want `arc` to span the full range in less than one physical turn. Values always **clamp** to `min…max` (no wrap). ### Keyboard Focused knob: | Key | Action | | --- | ------ | | `ArrowUp` / `ArrowRight` | Increase by `step` | | `ArrowDown` / `ArrowLeft` | Decrease by `step` | | `PageUp` | Increase by `step × 10` | | `PageDown` | Decrease by `step × 10` | | `Home` | `min` | | `End` | `max` | ### Wheel When the knob is focused, vertical wheel deltas step the value (same idea as key nudging). ## API reference `Knob` accepts **`size`** and **`className`** plus primitive props from `KnobPrimitive.RootProps`. ### Value and state | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `value` | `number` | - | Controlled value. | | `defaultValue` | `number` | - | Uncontrolled starting value; also the **double-tap reset** target when provided. | | `min` | `number` | `0` | Minimum. | | `max` | `number` | `100` | Maximum. | | `step` | `number` | `1` | Quantization step (drag, wheel, keyboard, reset). | | `disabled` | `boolean` | `false` | Disables pointer, wheel, and keyboard. | | `onValueChange` | `(value: number) => void` | - | Live updates while dragging or adjusting. | | `onValueCommit` | `(value: number) => void` | - | When the gesture ends (pointer up, wheel tick, reset, etc.). | | `aria-label` | `string` | - | Accessible name for the slider. | | `aria-labelledby` | `string` | - | ID of a labeling element. | ### Size and rail | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `size` | `"sm" \| "default" \| "lg" \| "xl"` | `"default"` | Diameter variant. | | `className` | `string` | - | On the root wrapper. | | `arcRadius` | `number` | `23` | Arc radius in the 48×48 viewBox. | | `arcStrokeWidth` | `number` | `3` | Track / arc stroke width. | | `angleGapRad` | `number` | `π/5` | Bottom gap in radians; drives default `angleOffset` / `angleRange`. | | `angleOffset` | `number` | from gap | Dial start angle in **degrees** (optional override). | | `angleRange` | `number` | from gap | Total sweep in **degrees** (optional override). | | `anchor` | `number` | - | Secondary value for the highlighted arc segment. | | `indicatorSpan` | `[number, number]` | `[0.24, 0.58]` | Needle inner / outer radius as fractions of track radius. | | `indicatorWidth` | `number` | `2.75` | Needle stroke width. | ### Pointer and drag | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `dragSensitivity` | `"arc" \| "revolution"` | `"arc"` | `arc`: one full span matches **`angleRange`** (as radians). `revolution`: one full span matches **360°** of pointer travel. Both are relative to the value at pointer down and clamp to `min…max`. | | `dragOptions` | see below | - | Pan vs rotate, dead zone, vertical sensitivity. | #### `dragOptions` (`Knob.DragOptions`) | Field | Default | Purpose | | ----- | ------- | ------- | | `verticalPanEnabled` | `false` | Allow vertical fader-style drag when `true`. | | `panSensitivityDivisor` | `150` | Vertical pan scale: `(max - min) / divisor` per unit delta. | | `centerDeadZoneRel` / `centerDeadZoneMinPx` | `0.08` / `2` | Hub radius where angle follows pointer without accumulating huge jumps. | | `modeLockMinPx` / `modeLockRel` | `6` / `0.055` | Movement before locking rotate vs pan mode. | | `panMinVerticalPx` | `10` | Minimum vertical movement to consider pan. | | `panDominanceRatio` | `2.5` | Pan wins when `movedY > movedX × ratio`. | Additional HTML attributes on the root are forwarded where valid. ## Examples ### Disabled state ### Size variants ### Rail, anchor, and range highlight Use `anchor` to show a reference point on the rail (for example **center** on a bipolar control). The arc highlights the interval between `anchor` and the current value. ### Pointer mapping Each preview shows one mapping in isolation (live value under the control). #### Default arc (circular) #### Vertical fader-style drag #### Revolution (360° reference) ### Reset with double-tap ### Fine step precision (`step={0.01}`) ### Live value vs committed value callbacks ### Filter cutoff ### Channel strip # Audio Player --- title: Audio Player description: A composable audio player. One component, one install, fully owned by you. component: true base: base audioProvider: true --- Everything you need to build an audio player is in a single file: the engine, the UI controls, the queue, tracks, and playback speed. No separate installs, no cross-file wiring. Just one component you copy, paste, and own. ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/player ``` The CLI resolves every dependency automatically: the store, the HTML audio engine, `Fader`, `Transport`, `SortableList`, and the shadcn/ui primitives the player uses. Install the runtime dependencies: ```bash npm install @phosphor-icons/react class-variance-authority @base-ui/react ``` Install the required shadcn/ui primitives: ```bash npx shadcn@latest add avatar badge button command dialog dropdown-menu empty item spinner toggle tooltip ``` Copy the control primitives too — [Fader](/docs/components/base/fader), [Transport](/docs/components/base/transport), and [Sortable List](/docs/components/base/sortable-list) — plus the `store` and `html` lib files and the `use-audio` / `use-audio-provider` hooks (see [Audio Store](/docs/lib/audio-store) and [HTML Audio](/docs/lib/html-audio)). Copy and paste the following code into your project. Update the import paths to match your project setup. ## Philosophy **Composable by default.** `AudioPlayer` is just a container. Every control (seek bar, volume, queue, playback speed) is an independent component you drop in where you need it. No black-box layout, no hidden markup. **State lives in the store, not in components.** All playback state (current track, progress, queue, shuffle, repeat) is held in a Zustand store. Components read and write that store directly. No prop drilling, no context threading. **The engine is separate from the UI.** `AudioProvider` wires up the HTML audio element to the store: event listeners, retries, preloading, state restoration. UI components just read state and call store actions. ## Setup Pass `tracks` directly to `AudioPlayer` for a self-contained widget: ```tsx import { AudioPlayer, AudioPlayerControlBar, AudioPlayerPlay } from "@/components/player"; ``` For shared state across multiple players or app-wide persistence, mount `AudioProvider` once at the root: ```tsx // app/layout.tsx import { AudioProvider } from "@/components/player"; export default function RootLayout({ children }) { return ( {children} ); } ``` tracks vs AudioProvider: Use tracks on AudioPlayer for self-contained widgets. Use AudioProvider at the root when multiple components share the same playback session. ## Player ### Compact ### With queue ### Variants ### Sizes ### AudioPlayer Container for all player controls. | Prop | Type | Default | Description | | ----------- | --------------------------------- | ----------- | ------------------------------------------------------------------- | | `tracks` | `Track[]` | - | When provided, wraps children with `AudioProvider` automatically. | | `variant` | `"default" \| "ghost"` | `"default"` | Visual style. `ghost` is transparent with a hover background. | | `size` | `"sm" \| "default"` | `"default"` | Controls padding and border radius. | | `className` | `string` | - | Additional CSS classes. | Inherits all other props from `HTMLDivElement`. The underlying CVA definition is exported as `audioPlayerVariants` for extension. ### AudioProvider Connects the HTML audio element to the Zustand store. Handles event listeners, retries, preloading, and state restoration. | Prop | Type | Default | Description | | ---------- | ----------- | ------- | ------------------------------------------- | | `tracks` | `Track[]` | `[]` | Initial tracks to populate the queue. | | `children` | `ReactNode` | - | Components that share this audio session. | ### AudioPlayerButton Shared button primitive used by all player controls. Wraps a `Button` with an optional tooltip. | Prop | Type | Default | Description | | -------------- | -------- | ------- | ------------------------------------------------ | | `tooltipLabel` | `string` | - | When provided, wraps the button in a tooltip. | Inherits all other props from `Button`. ### AudioPlayerControlBar Groups controls in a single row or stacked column layout. | Prop | Type | Default | Description | | --------- | ------------------------ | ----------- | ---------------------- | | `variant` | `"compact" \| "stacked"` | `"compact"` | Controls the layout. | ### AudioPlayerControlGroup Flexible row wrapper for arranging control clusters. Use `className` to adjust alignment. ### AudioPlayerPlay Play/pause button. Shows a spinner during loading/buffering. Spacebar shortcut is enabled automatically. Inherits all props from `AudioPlayerButton`. ### AudioPlayerSkipBack / AudioPlayerSkipForward Navigate between tracks. Inherits all props from `AudioPlayerButton`. ### AudioPlayerRewind / AudioPlayerFastForward Seek backward or forward by 10 seconds. Disabled for live streams. Inherits all props from `AudioPlayerButton`. ### AudioPlayerSeekBar Seek timeline showing playback and buffered progress. Locked to 100% for live streams. Inherits all props from [Transport](/docs/components/base/transport) (except `value`, `onSeek`, `bufferedValue`). ### AudioPlayerTimeDisplay Displays current time or remaining time. Shows a live indicator for streams. | Prop | Type | Default | Description | | ----------- | --------- | ------- | ----------------------------------------------- | | `remaining` | `boolean` | `false` | Show remaining time instead of elapsed time. | ### AudioPlayerVolume Dropdown with a horizontal fader to control volume and mute. Inherits all props from [Fader](/docs/components/base/fader) (except `value`, `onValueChange`, `min`, `max`, `orientation`, `size`). ## Tracks `AudioTrackList` renders a list of tracks. Omit `tracks` to read from the global queue; pass `tracks` for controlled mode. ### Single track ### Default ### Grid ### Sortable ### Sortable Grid ### AudioTrack Renders a single track row. `media` and `actions` are composed, not configured — pass the pieces you want. | Prop | Type | Default | Description | | ---------- | ---------------------------- | ------- | ------------------------------------------------- | | `trackId` | `string \| number` | - | Look up a track from the queue by id (store mode). | | `track` | `Track` | - | Render a provided track object (controlled mode). | | `index` | `number` | - | Display index, exposed to `AudioTrackIndex`. | | `onClick` | `() => void` | - | Called when the row is clicked. | | `onRemove` | `(trackId: string) => void` | - | Enables `AudioTrackRemoveAction` when provided. | | `media` | `React.ReactNode` | - | Left-side content, e.g. ``. | | `actions` | `React.ReactNode` | - | Right-side content, e.g. ``. | ```tsx } actions={} /> // Sortable row with remove support } actions={ <> } onRemove={handleRemove} /> ``` `AudioTrackCover`, `AudioTrackIndex`, `AudioTrackPlayPauseAction`, and `AudioTrackRemoveAction` read track state from context — they only work inside `AudioTrack`. `AudioTrackRemoveAction` renders nothing for the currently playing track or when `onRemove` isn't set. Mode constraint: Use either trackId or track, not both. trackId requires an AudioProvider in the tree. ### AudioTrackList | Prop | Type | Default | Description | | ------------------ | ----------------------------------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------- | | `tracks` | `Track[]` | - | Controlled list. Omit to read from the global queue. | | `onTrackSelect` | `(index: number, track?: Track) => void` | - | Called when a track is selected. | | `onTrackRemove` | `(trackId: string) => void` | - | Called when a track is removed. Enables remove actions automatically. | | `mode` | `"static" \| "sortable"` | `"static"` | Enable drag-and-drop reordering. | | `media` | `"cover" \| "index"` | `"cover"` | Media style. In sortable mode a drag handle is added automatically. | | `actions` | `"none" \| "play-pause" \| "remove" \| "play-pause-with-remove"` | Auto | Action variant per row. Auto: `"play-pause-with-remove"` when `onTrackRemove` is set. | | `variant` | `"default" \| "grid"` | `"default"` | Stacked list or responsive grid. | | `filterQuery` | `string` | - | Text filter (matches title or artist). | | `filterFn` | `(track: Track) => boolean` | - | Custom filter. Overrides `filterQuery` when both are set. | | `emptyLabel` | `string` | `"No tracks found"` | Empty state label. | | `emptyDescription` | `string` | `"Try adding some tracks"` | Empty state description. | Sortable constraint: Drag-and-drop reordering only updates the store when the list is unfiltered and in store mode. ## Queue All queue controls work together or independently. ### Simple ### All controls ### Shuffle and repeat ### Preferences ### AudioQueue A dialog with the full queue: search, selection, remove, and drag-and-drop reordering. | Prop | Type | Default | Description | | ------------------- | ------------------------- | ------------------------------------------ | ---------------------------------------------- | | `onTrackSelect` | `(index: number) => void` | - | Called when a track is selected from the queue. | | `searchPlaceholder` | `string` | `"Search for a track..."` | Search input placeholder. | | `emptyLabel` | `string` | `"No tracks found"` | Empty state label. | | `emptyDescription` | `string` | `"Try searching for a different track"` | Empty state description. | Search and reorder: Drag-and-drop is disabled while a search filter is active. ### AudioQueueShuffle Toggle that enables/disables shuffle. Persisted to localStorage. Inherits all props from `Toggle` (except `onPressedChange`). ### AudioQueueRepeatMode Toggle that cycles through repeat modes: `none` → `all` → `one`. Persisted to localStorage. Inherits all props from `Toggle`. ### AudioQueuePreferences Dropdown combining repeat mode and insert mode (`first`, `last`, `after current`) in one compact menu. Inherits all props from `AudioPlayerButton`. ## Playback Speed ### AudioPlaybackSpeed Dropdown to change playback speed. Disabled automatically for live streams. | Prop | Type | Default | Description | | --------- | ---------------------------------------------- | ------------------ | ------------------------------------------------------------ | | `speeds` | `readonly { value: number; label: string }[]` | `PLAYBACK_SPEEDS` | Speed options. Default: 0.5×, 0.75×, 1×, 1.25×, 1.5×, 2×. | | `size` | `ButtonSize` | - | `"icon"` hides the gauge icon, shows only the label. | | `variant` | `ButtonVariant` | `"outline"` | Button variant. | ## Notes Live streams: Seeking, rewind, fast-forward, and playback speed are disabled automatically. The seek bar locks to 100%. Spacebar shortcut: AudioPlayerPlay registers a global keydown listener for Space when the document body is focused. # Sortable List --- title: Sortable List description: A drag-and-drop sortable list component built with dnd-kit. component: true base: base --- ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/sortable-list ``` Install the following dependencies: ```bash npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities @phosphor-icons/react ``` Install the required shadcn/ui primitives: ```bash npx shadcn@latest add button ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ## Usage ```tsx import { SortableList, SortableItem, SortableDragHandle, } from "@/components/audio/sortable-list"; ``` ```tsx showLineNumbers "use client"; import { useState } from "react"; import { Item, ItemActions, ItemContent, ItemDescription, ItemTitle, } from "@/components/ui/item"; import { SortableList, SortableItem, SortableDragHandle, } from "@/components/audio/sortable-list"; const initialItems = [ { id: "1", title: "Item 1", description: "Description for item 1", }, { id: "2", title: "Item 2", description: "Description for item 2", }, { id: "3", title: "Item 3", description: "Description for item 3", }, ]; export function SortableListDemo() { const [items, setItems] = useState(initialItems); return ( ( {item.title} {item.description} )} /> ); } ``` ## API Reference ### SortableList The main container component for sortable items. Built with `@dnd-kit` for accessibility and smooth drag-and-drop interactions. #### Props | Prop | Type | Description | | ------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `items` | `TItem[]` | Array of items to display. Each item must have an `id` property. | | `onChange` | `(items: TItem[]) => void` | Callback fired when items are reordered. | | `renderItem` | `(item: TItem, index: number, isOverlay?: boolean) => React.ReactNode` | Function to render each item. The `isOverlay` parameter indicates when an item is being dragged. | | `className` | `string` | Additional CSS classes for the list container. | ### SortableItem A wrapper component for individual sortable items. #### Props | Prop | Type | Description | | ----------- | ------------------ | ----------------------------------------------------- | | `id` | `string \| number` | Unique identifier for the item (must match item.id). | | `className` | `string` | Additional CSS classes. | | `children` | `React.ReactNode` | Content of the sortable item. | ### SortableDragHandle A pre-built drag handle button that uses the sortable context. Extends shadcn/ui's `Button` component. #### Props Inherits all props from `Button` component. ## Notes Important Information: - **Custom Component:** This component is not part of the default shadcn/ui registry. It is custom to this project for drag-and-drop audio queue reordering. - **Built with dnd-kit:** This component is built on top of @dnd-kit, which includes keyboard-friendly drag-and-drop behavior. See the{" "} dnd-kit documentation for details. - **Drag Handle:** SortableDragHandle uses the project Button{" "} primitive and ships pre-wired accessibility attributes. - **Item IDs:** Each item must have a unique id property. The `id` type must match between the item object and the SortableItem{" "} component. - **Usage in Audio Components:** This component is used by{" "} AudioTrackList for queue reordering. # Transport --- title: Transport description: Timeline transport control for playback progress and scrubbing, with buffered range visualization, keyboard, wheel, and pointer interactions. component: true base: base --- Timeline control for playback progress (current value + buffered range). The registry component wraps `Transport` primitives from `@audio-ui/react` with audio-focused defaults for player seek bars and scrub interactions. ## Overview - **Interaction model:** Pointer drag on track/thumb, wheel stepping, keyboard nudging, and commit callbacks. - **Visual model:** Base track + buffered range + played range + thumb. - **Playback UX:** Supports `freezeValuesWhileDragging` to avoid jitter/flicker while scrubbing. - **Accessibility:** Thumb is exposed as `role="slider"` with `aria-valuenow`, `aria-valuemin`, and `aria-valuemax`. ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/transport ``` Install the following dependencies: ```bash npm install @audio-ui/react ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ## Usage ```tsx import { Transport } from "@/components/audio/transport"; ``` **Controlled** (typical in player UIs): ```tsx const [progress, setProgress] = useState(42); ``` ## Interaction ### Pointer | Behavior | Details | | -------- | ------- | | **Track click / drag** | Updates value from pointer position along the rail. | | **Thumb drag** | Continuous scrubbing, clamped to `min...max`. | | **Commit event** | `onValueCommit` fires when drag interaction ends (primitive-level callback). | ### Keyboard Focused thumb supports: | Key | Action | | --- | ------ | | `ArrowUp` / `ArrowRight` | Increase by `step` | | `ArrowDown` / `ArrowLeft` | Decrease by `step` | | `PageUp` | Increase by `step × 10` | | `PageDown` | Decrease by `step × 10` | | `Home` | Set to `min` | | `End` | Set to `max` | ### Wheel When focused, wheel input nudges the value by `step`. ## API reference `Transport` keeps an audio-focused API (`value`, `bufferedValue`, `onSeek`) and forwards remaining root props to `TransportPrimitive.Root`. ### Value and state | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `value` | `number` | - | Current transport value (typically `0..100`). | | `bufferedValue` | `number` | `0` | Buffered position (same scale as `value`). | | `onSeek` | `(nextValue: number) => void` | - | Called when scrubbing or keyboard/wheel changes value. | | `freezeValuesWhileDragging` | `boolean` | `false` | Freezes rendered values during active drag for smoother visual feedback. | | `disabled` | `boolean` | `false` | Disables interaction. | | `aria-label` | `string` | - | Accessible name for the slider. | | `aria-labelledby` | `string` | - | ID of the associated label element. | For production usage, pass either `aria-label` or `aria-labelledby` explicitly. ### Layout and behavior | Prop | Type | Default | Description | | ---- | ---- | ------- | ----------- | | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` (registry wrapper) | Direction of travel. | | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Thumb/rail density preset. | | `min` | `number` | `0` | Minimum value. | | `max` | `number` | `100` | Maximum value. | | `step` | `number` | `1` | Quantization step for drag/wheel/keyboard. | | `className` | `string` | - | Wrapper class name. | ## Examples ### Basic transport timeline ### Channel strip #### Horizontal #### Vertical # XY Pad --- title: XY Pad description: Two-dimensional control surface for mapping and automating X/Y parameters with pointer, wheel, and keyboard input. component: true base: base --- XY pad for simultaneous control of two values (for example filter cutoff/resonance, pan/mix, or macro controls). The registry component wraps `XYPad` primitives from `@audio-ui/react` and provides ready-to-use visuals (grid, crosshair, cursor, value display). ## Overview - **Interaction model:** Pointer drag, wheel nudging, and keyboard nudging over both axes. - **Axes:** Independent ranges and steps for X (`minX`, `maxX`, `stepX`) and Y (`minY`, `maxY`, `stepY`). - **Feedback:** Grid lines, crosshair, animated cursor, and optional value display overlay. - **Accessibility:** Focusable interactive surface with ARIA labeling (`aria-label` / `aria-labelledby`). ## Installation CLI Manual ```bash component npx shadcn@latest add @audio/xypad ``` Install the following dependencies: ```bash npm install @audio-ui/react class-variance-authority ``` Copy and paste the following code into your project. Update the import paths to match your project setup. ## Usage ```tsx import { XYPad } from "@/components/audio/xypad"; ``` **Uncontrolled** - initial value only: ```tsx ``` **Controlled** - drive value from state: ```tsx const [position, setPosition] = useState({ x: 50, y: 50 }); console.log("Committed:", value)} value={position} /> ``` ## Interaction ### Pointer | Behavior | Details | | -------- | ------- | | **Click / drag on pad** | Pointer position maps directly to X/Y in bounds. | | **Clamping** | Values always clamp to configured axis ranges. | | **Commit event** | `onValueCommit` fires when drag interaction ends. | ### Keyboard Focused XY pad: | Key | Action | | --- | ------ | | `ArrowLeft` / `ArrowRight` | Decrease / increase X by `stepX` | | `ArrowUp` / `ArrowDown` | Increase / decrease Y by `stepY` | | `PageUp` / `PageDown` | Increase / decrease Y by `stepY × 10` | | `Home` | Jump to top-left logical corner (`x = minX`, `y = maxY`) | | `End` | Jump to bottom-right logical corner (`x = maxX`, `y = minY`) | ### Wheel When focused, wheel deltas nudge both axes according to pad dimensions and axis ranges, then quantize with `stepX` and `stepY`. ## API reference `XYPad` accepts style props (`size`, `formatValue`, `valueDisplay`, `className`) plus primitive props from `XYPadPrimitive.RootProps`. ### Value and state | Prop | Type | Default | Description | | --------------- | --------------------------------------- | ------------- | ------------------------------------------------------------------ | | `value` | `{ x: number; y: number }` | - | Controlled value. | | `defaultValue` | `{ x: number; y: number }` | `{ x: 0, y: 0 }` | Uncontrolled default value. | | `minX` | `number` | `0` | Minimum X value. | | `maxX` | `number` | `100` | Maximum X value. | | `minY` | `number` | `0` | Minimum Y value. | | `maxY` | `number` | `100` | Maximum Y value. | | `stepX` | `number` | `1` | Step increment for X axis. | | `stepY` | `number` | `1` | Step increment for Y axis. | | `disabled` | `boolean` | `false` | Whether the pad is disabled. | | `onValueChange` | `(value: { x: number; y: number }) => void` | - | Callback when value changes during interaction. | | `onValueCommit` | `(value: { x: number; y: number }) => void` | - | Callback when value is committed (on release). | | `aria-label` | `string` | `"XY Pad"` | Accessible name for the interactive surface. | | `aria-labelledby` | `string` | - | ID of a labeling element. | ### Layout and display | Prop | Type | Default | Description | | --------------- | --------------------------------------- | ------------- | ------------------------------------------------------------------ | | `size` | `"sm" \| "default" \| "lg" \| "xl"` | `"default"` | Size variant (affects height). | | `formatValue` | `(value: { x: number; y: number }) => React.ReactNode` | - | Custom formatter for value display. | | `valueDisplay` | `"visible" \| "hidden"` | `"visible"` | Controls value display visibility. Use `"hidden"` to hide overlay. | | `className` | `string` | - | Additional CSS classes. | Additional valid HTML attributes passed to `XYPad.Root` are forwarded. ## Examples ### Live value readout ### Size variants #### `sm` #### `default` #### `lg` #### `xl` ### Disabled state ### Custom formatted display ### Hidden value display ### Bipolar ranges (pan and mix style) ### Live value vs committed value callbacks ### Channel strip #### Filter pad #### Reverb pad