# File Upload

Drop or browse files and follow each upload’s progress.

Upload area with file rows

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/file-upload-demo
```

This Pattern is part of [Siteplane UI Pro](/pricing), and its source comes with a [Pro license](/docs/license/#patterns). Its previews are free to view and try.

## Installation

**Pro** — Installs with a Siteplane UI Pro license key. Complete the one-time [Pro setup](/docs/installation/#siteplane-ui-pro) first. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/file-upload
```

With npm: `npx shadcn@4.18.0 add @siteplane-pro/file-upload`.

## Usage

```tsx
import {
  FileUpload,
  type UploadFile,
} from "@/siteplane/components/file-upload";

const files: UploadFile[] = [
  { id: "brief", name: "Brief.pdf", sizeLabel: "1.2 MB", status: "complete" },
];

<FileUpload
  accept="application/pdf,image/*"
  description="PDF or image, up to 10 MB."
  files={files}
  maxSize={10 * 1024 * 1024}
  onFilesSelected={(selected) => {
    // Start your own uploads, then update `files` as they progress.
    console.log(selected);
  }}
/>;
```

## Composition

FileUpload renders every part itself. You supply the files, their status and the handlers.

```text
FileUpload
├── Dropzone (Card)
│   ├── Icon
│   ├── Browse button and hidden file input
│   └── Description
├── Rejection messages (live region)
└── File list
    └── Row (Card)
        ├── FileTypeIcon
        ├── Name, size and status
        ├── Progress
        ├── Try again (failed rows)
        └── Remove button
```

## Progress

Each row shows a progress bar by default. Set `variant="fill"` to show progress as a tint that fills the row from its start edge, with the percentage in the status line; the tint fades out once the upload completes or fails. The bar stays available to assistive technology in both variants.

Report the real progress of your upload request in `files`. The examples on this page use a timer instead of a request.

Uploads with fill progress

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/file-upload-progress-fill-example
```

## Compact dropzone

Set `compact` for a shorter dropzone without the icon. It is made for a row where the zone sits beside an avatar or a label. With `multiple={false}` only the first file of a selection or drop counts, which suits a single profile photo, and the dropzone lists any other file as rejected. This example shows the chosen image as the avatar instead of listing a row.

Profile photo with a compact dropzone

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/file-upload-compact-example
```

## Accepted files and size limit

`accept` takes MIME types, wildcards such as `image/*` and extensions such as `.pdf`, like a native file input, and also filters the browser’s file dialog. `maxSize` is a limit per file in bytes. A file that fails either check never reaches `onFilesSelected`. The dropzone lists it below with the reason, and `onRejected` receives it. State the limits in `description` so people see them before they choose files.

Choose a file that is not a PNG or JPG, or larger than 2 MB, to see the messages.

Image upload with type and size limits

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/file-upload-restrictions-example
```

## Surface

The dropzone and the rows use the `inset` surface by default, so they press into the surface that holds them, such as a Card, a Dialog or a Sheet. Set `surface="raised"` when the upload sits directly on the page background and should lift like a Card. A row whose upload failed keeps its error surface with either value.

Raised upload area on the page background

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/file-upload-raised-example
```

## Disabled

Set `disabled` to switch the upload off, for example for an archived project. The dropzone keeps its surface while its icon and text dim, and its browse button, drops, Try again and remove do nothing. Rows already in the list stay visible.

Disabled upload area

**Pro** — This example installs with a Siteplane UI Pro license key. Get a license on the [Pro page](/pricing).

```bash
pnpm dlx shadcn@4.18.0 add @siteplane-pro/file-upload-disabled-example
```

## API reference

### FileUpload

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| files | readonly UploadFile[] | [] | Rows listed under the dropzone, in the order you pass them. The application owns the list, each status and each progress value. |
| onFilesSelected | (files: File[]) => void | unset | Called after a selection or a drop with the files that pass accept and maxSize; while multiple is off, only the first file can pass. Start the uploads here. |
| onRejected | (files: FileRejection[]) => void | unset | Called with the files that were not taken. A file's reason is type when accept excludes it, size when it is over maxSize and count when it is a surplus file while multiple is off. The dropzone also lists them below itself. |
| onRemove | (id: string) => void | unset | Shows a remove button on every row and receives the row’s id. Stop the upload and update files. |
| onRetry | (id: string) => void | unset | Shows Try again on a failed row and receives the row’s id. |
| accept | string | unset | Comma-separated MIME types, type/* wildcards or .extensions, as for a native file input. |
| maxSize | number | unset | Largest accepted file size in bytes. |
| multiple | boolean | true | Accept several files at once. When false, only the first file of a selection or drop is taken, and the others are rejected with the reason count. |
| disabled | boolean | false | Switches off the dropzone, Try again and remove. |
| description | ReactNode | unset | Hint under the dropzone text, such as the allowed types and size. |
| variant | bar \| fill | bar | Progress as a bar in each row or as a fill behind it. |
| compact | boolean | false | Shorter dropzone without the icon, for a row beside an avatar or a label. |
| surface | inset \| raised | inset | Card surface of the dropzone and the rows. A failed row keeps its error surface. |
| labels | Partial<FileUploadLabels> | English text | Every visible and spoken string; see the keys below. |

FileUploadProps extends native div props except onSelect, so `className`, `id` and `aria-*` reach the root. The component is not a form field: it submits nothing and has no `name` or `value`. A `FileRejection` holds the `file` and its `reason`.

The keys of `labels`, each with its English default, are `select` (Click to upload), `drop` (or drag and drop), `uploading` (Uploading…), `complete` (Complete), `failed` (Upload failed), `retry` (Try again), `remove` (Remove), `files` (Files, the name of the list), `rejectedType` (File type not accepted), `rejectedSize` (File exceeds size limit) and `rejectedCount` (Only one file can be uploaded). `remove` and `retry` are followed by the file name in each button’s accessible name, and each rejection message starts with the file name. With `variant="fill"`, a row shows its percentage in place of `uploading` and `complete`.

### UploadFile

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| id | string | required | Stable key of the row, passed to onRemove and onRetry. |
| name | string | required | File name. Its extension chooses the FileTypeIcon, and it names the progress bar. |
| sizeLabel | string | required | Size as text, formatted by the application, such as 720 KB. |
| status | uploading \| complete \| error | required | State of the row. A complete row shows 100%. |
| progress | number | 0 | Percent from 0 to 100 while uploading. Values outside the range are clamped. |
| error | string | Upload failed | Message of a failed row, in place of labels.failed. |

## Usage notes

FileUpload does not upload anything. Start the request in `onFilesSelected`, report progress and the final status through `files`, and own cancellation, retries and server-side validation. Removing a row only calls `onRemove`; abort the request there.

`accept` and `maxSize` help the person choosing files. They are not a security check, so validate type and size again on the server.

Give every row a stable `id`: rows animate in and out by key. Format `sizeLabel` and error messages for your locale and pass translated strings through `labels`.

The dropzone is operated through its browse button; the hidden file input is not a tab stop. Rejection messages appear below the dropzone in a polite live region. After a row is removed, focus moves to a neighboring row or back to the browse button, and after Try again it moves to the row’s remove button, or to the browse button when the row has none. With reduced motion, rows, rejection messages and the fill change without animation.

The file rows use [File Type Icon](/docs/components/file-type-icon/).
