# Upload (/docs/components/upload)



<ComponentPreview name="upload-demo" />

## Installation [#installation]

<Tabs items="[&#x22;Command&#x22;, &#x22;Manual&#x22;]">
  <Tab value="Command">
    <CodeBlockTabs defaultValue="npm">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npx shadcn@latest add @uploadcn/upload
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm dlx shadcn@latest add @uploadcn/upload
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn dlx shadcn@latest add @uploadcn/upload
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun x shadcn@latest add @uploadcn/upload
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Tab>

  <Tab value="Manual">
    Install the dependencies:

    <CodeBlockTabs defaultValue="npm">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npm install @uploadcn/core @uploadcn/react cn class-variance-authority lucide-react
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm add @uploadcn/core @uploadcn/react cn class-variance-authority lucide-react
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn add @uploadcn/core @uploadcn/react cn class-variance-authority lucide-react
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun add @uploadcn/core @uploadcn/react cn class-variance-authority lucide-react
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    Add the shadcn `button` component, then copy this file into your project:

    <ComponentSource name="upload" />
  </Tab>
</Tabs>

## Usage [#usage]

```tsx
import { s3Adapter } from "@uploadcn/core"

import {
  Upload,
  UploadDropzone,
  UploadDropzoneDescription,
  UploadDropzoneHeader,
  UploadDropzoneMedia,
  UploadDropzoneTitle,
  UploadItem,
  UploadItemActions,
  UploadItemContent,
  UploadItemDescription,
  UploadItemMedia,
  UploadItemProgress,
  UploadItemTitle,
  UploadList,
} from "@/components/ui/upload"
```

```tsx
<Upload adapter={s3Adapter({ endpoint: "/api/upload" })} maxFiles={8}>
  <UploadDropzone>
    <UploadDropzoneHeader>
      <UploadDropzoneMedia variant="icon" />
      <UploadDropzoneTitle>Drop files here</UploadDropzoneTitle>
      <UploadDropzoneDescription>or click to browse</UploadDropzoneDescription>
    </UploadDropzoneHeader>
  </UploadDropzone>
  <UploadList>
    <UploadItem>
      <UploadItemMedia />
      <UploadItemContent>
        <UploadItemTitle />
        <UploadItemDescription />
      </UploadItemContent>
      <UploadItemActions />
      <UploadItemProgress />
    </UploadItem>
  </UploadList>
</Upload>
```

## Composition [#composition]

`Upload` holds the uploader. Everything else is a part you place where you want it.

<ComponentTree
  root="{
  name: &#x22;Upload&#x22;,
  description: &#x22;holds the uploader, options or an `uploader` prop&#x22;,
  children: [
    {
      name: &#x22;UploadDropzone&#x22;,
      description: &#x22;drag, drop, click, Enter / Space&#x22;,
      children: [
        {
          name: &#x22;UploadDropzoneHeader&#x22;,
          children: [
            { name: &#x22;UploadDropzoneMedia&#x22; },
            { name: &#x22;UploadDropzoneTitle&#x22; },
            { name: &#x22;UploadDropzoneDescription&#x22; },
          ],
        },
        {
          name: &#x22;UploadDropzoneContent&#x22;,
          description: &#x22;buttons, e.g. UploadTrigger&#x22;,
        },
      ],
    },
    { name: &#x22;UploadTrigger&#x22;, description: &#x22;opens the file picker&#x22; },
    {
      name: &#x22;UploadOverlay&#x22;,
      description: &#x22;turns any content into a drop target&#x22;,
    },
    {
      name: &#x22;UploadSlot&#x22;,
      description: &#x22;a named single-file slot, e.g. “ID front”&#x22;,
    },
    {
      name: &#x22;UploadList&#x22;,
      description: &#x22;renders its children once per file&#x22;,
      children: [
        {
          name: &#x22;UploadItem&#x22;,
          children: [
            {
              name: &#x22;UploadItemMedia&#x22;,
              description: &#x22;thumbnail or file-type icon&#x22;,
            },
            {
              name: &#x22;UploadItemContent&#x22;,
              children: [
                { name: &#x22;UploadItemTitle&#x22;, description: &#x22;file name&#x22; },
                {
                  name: &#x22;UploadItemDescription&#x22;,
                  description: &#x22;size · progress · speed · error&#x22;,
                },
              ],
            },
            {
              name: &#x22;UploadItemActions&#x22;,
              description: &#x22;pause, resume, retry, cancel, remove&#x22;,
            },
            { name: &#x22;UploadItemProgress&#x22; },
          ],
        },
      ],
    },
    {
      name: &#x22;UploadHeader / UploadFooter&#x22;,
      children: [
        {
          name: &#x22;UploadSummary&#x22;,
          description: &#x22;“3 files · 12.4 MB · 45% · 8s left”&#x22;,
        },
      ],
    },
    { name: &#x22;UploadProgress&#x22;, description: &#x22;all files combined&#x22; },
    { name: &#x22;UploadEmpty / UploadStart / UploadClear&#x22; },
  ],
}"
/>

`UploadList` renders its children as a template for every file and provides that file
to the parts inside. There's no render function or prop drilling. Each row re-renders
only when its own file changes.

Parts have sensible defaults and accept children to override them:

```tsx
<UploadItemTitle />                         {/* the file name */}
<UploadItemTitle>Invoice #1042</UploadItemTitle>

<UploadItemActions />                       {/* the applicable actions */}
<UploadItemActions>
  <UploadItemRetry />
  <UploadItemRemove />
</UploadItemActions>
```

## Dropzone [#dropzone]

### Variant [#variant]

Use `variant` to change the surface: `default` (dashed), `muted` or `outline`.

<ComponentPreview name="upload-dropzone-variants" />

### Orientation [#orientation]

`orientation="horizontal"` puts the content side by side, handy for compact forms.
Use `clickable={false}` when the dropzone contains its own `UploadTrigger`.

<ComponentPreview name="upload-dropzone-horizontal" />

### Size [#size]

`size` is `sm`, `default` or `lg`.

```tsx
<UploadDropzone size="sm">…</UploadDropzone>
```

## Item [#item]

### Variant [#variant-1]

`outline` (default), `muted`, `default` (no surface), and `tile` for grids.

<ComponentPreview name="upload-item-variants" />

### Size [#size-1]

`default`, `sm` and `xs`. Media, text and actions scale with the item.

<ComponentPreview name="upload-item-sizes" />

### Tiles [#tiles]

Combine `UploadList variant="grid"` with `UploadItem variant="tile"` and
`UploadItemMedia variant="cover"`.

<ComponentPreview name="upload-grid" />

### Chips [#chips]

`UploadList variant="inline"` with extra-small items makes attachment chips.

<ComponentPreview name="upload-chips" />

## Trigger [#trigger]

`UploadTrigger` renders an outline `Button`. Pass `render` to use any element.

<ComponentPreview name="upload-trigger" />

## Overlay [#overlay]

`UploadOverlay` makes any content a drop target without making it a button. A frosted
layer with a label covers the content only while files are dragged over it, so text
stays selectable and links stay clickable.

<ComponentPreview name="upload-overlay" />

```tsx
<UploadOverlay
  label="Drop to attach"
  description="Files are added to this note"
>
  <Note />
</UploadOverlay>
```

## Slots [#slots]

`UploadSlot` is a named, single-file slot inside a shared `<Upload>`, for forms
that need specific documents. Each slot has its own picker and drop target, replaces
its own file, and tags it with `meta.slot` so your backend knows which is which.
Read a slot's file with `useUploadSlot(name)`.

<ComponentPreview name="upload-slots" />

```tsx
<Upload>
  <UploadSlot
    name="id"
    label="Photo ID"
    accept="image/*,application/pdf"
    required
  />
  <UploadSlot name="address" label="Proof of address" />
</Upload>
```

## Header, footer and summary [#header-footer-and-summary]

`UploadHeader` and `UploadFooter` lay out titles and queue actions. `UploadSummary`
describes the queue in one line and renders nothing while it's empty.

<ComponentPreview name="upload-card" />

## Accessibility [#accessibility]

* `UploadDropzone` is a button: it's focusable and opens the picker with
  <kbd>Enter</kbd> or <kbd>Space</kbd>, the alternative to dragging.
* `UploadItemProgress` is a `progressbar` labelled with the file name.
* A polite live region announces added, finished, failed and rejected files.
* Every action has a name such as “Retry upload of report.pdf”.
* Motion respects `prefers-reduced-motion`.

## API Reference [#api-reference]

### Upload [#upload]

The root. Accepts every [uploader option](/docs/reference/core#uploaderoptions),
`adapter`, `accept`, `maxSize`, `maxFiles`, `concurrency`, `retry`, `transform`,
`process`, `persistence`…, or an `uploader` from [`useUploader`](/docs/hooks#useuploader).

<TypeTable
  type="{
  adapter: {
    type: &#x22;UploadAdapter&#x22;,
    description: &#x22;Where files go. Required unless `uploader` is passed.&#x22;,
  },
  uploader: {
    type: &#x22;Uploader&#x22;,
    description: &#x22;An uploader you control from outside.&#x22;,
  },
  multiple: {
    type: &#x22;boolean&#x22;,
    default: &#x22;true (false when maxFiles is 1)&#x22;,
    description: &#x22;In single mode a new file replaces the current one.&#x22;,
  },
  disabled: { type: &#x22;boolean&#x22;, default: &#x22;false&#x22; },
  name: {
    type: &#x22;string&#x22;,
    description:
      &#x22;Submit successful uploads as hidden inputs (native forms, server actions).&#x22;,
  },
  messages: {
    type: &#x22;Partial<UploadMessages>&#x22;,
    description: &#x22;Translate every label and announcement.&#x22;,
  },
}"
/>

### UploadDropzone [#uploaddropzone]

<TypeTable
  type="{
  variant: { type: '&#x22;default&#x22; | &#x22;muted&#x22; | &#x22;outline&#x22;', default: '&#x22;default&#x22;' },
  orientation: { type: '&#x22;vertical&#x22; | &#x22;horizontal&#x22;', default: '&#x22;vertical&#x22;' },
  size: { type: '&#x22;sm&#x22; | &#x22;default&#x22; | &#x22;lg&#x22;', default: '&#x22;default&#x22;' },
  clickable: {
    type: &#x22;boolean&#x22;,
    default: &#x22;true&#x22;,
    description: &#x22;Click, Enter and Space open the picker.&#x22;,
  },
}"
/>

Data attributes: `data-dragging`, `data-drag-reject`, `data-disabled`.

### UploadDropzoneMedia [#uploaddropzonemedia]

<TypeTable
  type="{
  variant: {
    type: '&#x22;default&#x22; | &#x22;icon&#x22;',
    default: '&#x22;default&#x22;',
    description:
      &#x22;`icon` draws a bordered circle that lifts on hover and drag.&#x22;,
  },
}"
/>

### UploadList [#uploadlist]

<TypeTable
  type="{
  variant: { type: '&#x22;list&#x22; | &#x22;grid&#x22; | &#x22;inline&#x22;', default: '&#x22;list&#x22;' },
  filter: {
    type: &#x22;(item) => boolean&#x22;,
    description: &#x22;Show only matching files.&#x22;,
  },
  children: {
    type: &#x22;ReactNode | (item) => ReactNode&#x22;,
    description: &#x22;A template rendered per file, or a function of the file.&#x22;,
  },
}"
/>

### UploadItem [#uploaditem]

<TypeTable
  type="{
  variant: {
    type: '&#x22;outline&#x22; | &#x22;muted&#x22; | &#x22;default&#x22; | &#x22;tile&#x22;',
    default: '&#x22;outline&#x22;',
  },
  size: { type: '&#x22;default&#x22; | &#x22;sm&#x22; | &#x22;xs&#x22;', default: '&#x22;default&#x22;' },
  item: {
    type: &#x22;UploadItem&#x22;,
    description: &#x22;Only needed outside UploadList.&#x22;,
  },
}"
/>

Data attributes: `data-status` (`uploading`, `success`, `error`…), `data-variant`, `data-size`.

### UploadItemMedia [#uploaditemmedia]

<TypeTable
  type="{
  variant: {
    type: '&#x22;default&#x22; | &#x22;icon&#x22; | &#x22;cover&#x22;',
    default: '&#x22;default&#x22;',
    description:
      &#x22;`default` shows a thumbnail for images and an icon otherwise.&#x22;,
  },
  thumbnailSize: { type: &#x22;number&#x22;, default: &#x22;128 (400 for cover)&#x22; },
}"
/>

### UploadItemProgress [#uploaditemprogress]

<TypeTable
  type="{
  forceMount: {
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
    description: &#x22;Keep the bar after the file finished.&#x22;,
  },
}"
/>

### UploadOverlay [#uploadoverlay]

<TypeTable
  type="{
  label: { type: &#x22;ReactNode&#x22;, default: '&#x22;Drop files to upload&#x22;' },
  description: { type: &#x22;ReactNode&#x22; },
}"
/>

### UploadSlot [#uploadslot]

<TypeTable
  type="{
  name: {
    type: &#x22;string&#x22;,
    description: &#x22;Stored on the file as `meta.slot`.&#x22;,
    required: true,
  },
  label: { type: &#x22;ReactNode&#x22;, required: true },
  description: { type: &#x22;ReactNode&#x22; },
  accept: {
    type: &#x22;string | string[]&#x22;,
    description: &#x22;Checked before the file is added.&#x22;,
  },
  icon: { type: &#x22;ReactNode&#x22; },
  required: {
    type: &#x22;boolean&#x22;,
    description: &#x22;Adds “(required)” to the label.&#x22;,
  },
  children: {
    type: &#x22;ReactNode&#x22;,
    description:
      &#x22;Content for a filled slot. `UploadItem*` parts work inside.&#x22;,
  },
}"
/>

### UploadSummary [#uploadsummary]

Pass children to replace the text. Without children it renders nothing for an empty
queue.

### Actions [#actions]

`UploadItemPause`, `UploadItemResume`, `UploadItemRetry`, `UploadItemCancel` and
`UploadItemRemove` render only when they apply, pause only for resumable adapters.
They accept `Button` props (`variant`, `size`) and `forceMount`.

## Headless [#headless]

The same parts without styles live in `@uploadcn/react`, see the
[custom UI example](/examples#custom-upload-ui).
