# Audio Upload

> Audio uploads with an inline player per file.

A shadcn/ui block from the [@uploadcn](https://uploadcn.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uploadcn/audio-upload).

- Page, with a live preview: https://shadcn.exchange/r/uploadcn/audio-upload
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uploadcn/audio-upload-light-4a7b120e2a2b0ff6.png
- Registry item: https://uploadcn.dev/r/new-york-v4/audio-upload.json
- Categories: upload, media
- Listed: 2026-10-14, source last changed 2026-10-14

## Install

```bash
npx shadcn@latest add https://uploadcn.dev/r/new-york-v4/audio-upload.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@uploadcn/core`, `@uploadcn/react`
- Registry items: `@uploadcn/upload`

## Files

### `registry/default/blocks/audio-upload.tsx`

```tsx
"use client"

import * as React from "react"
import {
  type UploadAdapter,
  type Uploader,
  type UploadItem as UploadItemData,
} from "@uploadcn/core"
import {
  useFilePreview,
  useUploadItem,
  type UploadRootProps,
} from "@uploadcn/react"

import { IconPlaceholder } from "@/components/icon-placeholder"
import {
  Upload,
  UploadDropzone,
  UploadDropzoneDescription,
  UploadDropzoneHeader,
  UploadDropzoneMedia,
  UploadDropzoneTitle,
  UploadItem,
  UploadItemActions,
  UploadItemContent,
  UploadItemDescription,
  UploadItemMedia,
  UploadItemProgress,
  UploadItemTitle,
  UploadList,
} from "@/registry/default/ui/upload"

interface AudioUploadProps<TResult> {
  adapter?: UploadAdapter<TResult>
  /** Use an uploader you created with `useUploader` instead of options. */
  uploader?: Uploader<TResult>
  /** Longest allowed recording, in seconds. */
  maxDuration?: number
  maxFiles?: number
  /** Default 500 MB. */
  maxSize?: number
  onSuccess?: (item: UploadItemData<TResult>) => void
  className?: string
}

/** Audio uploads (podcasts, voice notes, music) with an inline player per file. */
function AudioUpload<TResult = unknown>({
  adapter,
  uploader,
  maxDuration,
  maxFiles = 10,
  maxSize = 500 * 1000 * 1000,
  onSuccess,
  className,
}: AudioUploadProps<TResult>) {
  return (
    <Upload
      {...((uploader
        ? { uploader }
        : {
            adapter,
            accept: "audio/*",
            maxFiles,
            maxSize,
            media: maxDuration ? { maxDuration } : undefined,
            onSuccess,
          }) as UploadRootProps<TResult>)}
      className={className}
    >
      <UploadDropzone orientation="horizontal" size="sm">
        <UploadDropzoneHeader>
          <UploadDropzoneMedia variant="icon">
            <IconPlaceholder
              lucide="MicIcon"
              tabler="IconMicrophone"
              hugeicons="VoiceIcon"
              phosphor="MicrophoneIcon"
              remixicon="RiMicLine"
            />
          </UploadDropzoneMedia>
          <div className="flex flex-col gap-0.5">
            <UploadDropzoneTitle>Add audio</UploadDropzoneTitle>
            <UploadDropzoneDescription>
              MP3, WAV, M4A, OGG or FLAC
            </UploadDropzoneDescription>
          </div>
        </UploadDropzoneHeader>
      </UploadDropzone>
      <UploadList>
        <UploadItem>
          <UploadItemMedia variant="icon" />
          <UploadItemContent>
            <UploadItemTitle />
            <UploadItemDescription />
          </UploadItemContent>
          <UploadItemActions />
          <UploadItemProgress />
          <AudioPlayer />
        </UploadItem>
      </UploadList>
    </Upload>
  )
}

function AudioPlayer() {
  const item = useUploadItem()
  const url = useFilePreview(
    item.status === "rejected" ? null : item.originalFile
  )
  if (!url) return null
  return (
    <audio
      src={url}
      controls
      preload="metadata"
      className="h-9 basis-full"
      aria-label={`Play ${item.name}`}
    />
  )
}

export { AudioUpload, type AudioUploadProps }
```
