# FileUpload

Pick files with a dropzone: drag them in, or click or press Enter on it to open the file picker. Files that are too large or of the wrong type are turned down with a reason. Picked files list below with their upload progress, an uploaded mark or the error with a retry button, and a remove button named with each file.

**Dependencies:** npm: 2 direct, 4 total; registry: 5 direct, 5 total.

## Usage

### When to use

- Let people add files to a form, a ticket or a project, by dragging them in or picking them.
- Show each file's upload progress, and let a failed upload be retried or removed.

### When not to use

- Attach files to a chat message. Use Composer with AttachmentPreview.
- Show files already sent, as links. Use AttachmentPreview with href.
- Pick one avatar that should be seen large. Show the image with a change Button.

## Variants

### size

Dropzone padding, icon and text: sm for a side panel, lg for a page that is mostly the upload.

Values: `sm`, `md`, `lg`. Default: `md`.

## Examples

### Images

accept and maxSize turn down other files with a reason, and hint says the rules up front.

```tsx
<FileUpload multiple accept="image/*" maxSize={5000000} hint="PNG, JPG or GIF, up to 5 MB" onFilesAdd={uploadFiles} />
```

### Upload progress

Each file shows its progress, an uploaded mark, or its error with a retry button.

```tsx
<FileUpload multiple defaultValue={sampleUploads} onRetry={retryUpload} hint="Up to 25 MB each" />
```

### With a label

Name the field with Label and inputId. maxFiles 1 keeps one file.

```tsx
<div className="flex w-full flex-col gap-xs">
  <Label htmlFor="resume">Resume</Label>
  <FileUpload inputId="resume" accept=".pdf,.doc,.docx" maxFiles={1} hint="PDF or Word, up to 10 MB" maxSize={10000000} />
</div>
```

### Invalid

invalid turns the edge to danger, and error says why under the dropzone.

```tsx
<FileUpload invalid error="Add at least one receipt" hint="PDF or image" accept="application/pdf,image/*" />
```

### Disabled

```tsx
<FileUpload disabled hint="Uploads open after the review" />
```

### In a side panel

size sm keeps the dropzone short.

```tsx
<FileUpload size="sm" multiple defaultValue={sampleUploadsDone} hint="Up to 25 MB each" />
```

## Do and don't

### Do

- Say the accepted types and the size limit in hint, so nobody finds out by failing.
- Start uploads in onFilesAdd and show each file's progress in value.
- Keep failed files in the list with the reason, and pass onRetry when a retry can work.

### Don't

- Do not hide the input or replace it with a div; the real input gives the picker, the keyboard and forms for free.
- Do not remove a failed file on its own; let the person remove it.
- Do not use it for one avatar or cover photo that should be seen large; show the image and a change button instead.

## Props

| Name | Type | Values | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `FileUploadItem[]` |  |  | The files, controlled. Each item has id, name, type, size, src, status (uploading, done or error), progress and error. |
| `defaultValue` | `FileUploadItem[]` |  |  | The first files, uncontrolled. |
| `onValueChange` | `(items: FileUploadItem[]) => void` |  |  | Called with the whole list after files are added or removed. |
| `onFilesAdd` | `(items: FileUploadItem[]) => void` |  |  | Called with the accepted new files, to start their uploads. |
| `onRemove` | `(item: FileUploadItem) => void` |  |  | Called when a remove button is pressed, before the list changes, to cancel an upload. |
| `onRetry` | `(item: FileUploadItem) => void` |  |  | Shows a retry button on failed uploads and is called when it is pressed. |
| `accept` | `string` |  |  | File types, as on the input, such as image/*,.pdf. Dropped files of other types are turned down. |
| `maxSize` | `number` |  |  | The largest file in bytes. Larger files are listed with an error. |
| `maxFiles` | `number` |  |  | The most files in the list. Files over it are not added, and a notice says so. |
| `multiple` | `boolean` |  | `false` | Allows more than one file. Without it a new file takes the place of the old one. |
| `disabled` | `boolean` |  | `false` | Turns off the dropzone and the buttons. |
| `invalid` | `boolean` |  | `false` | Marks the field invalid, with a danger edge. Pair it with error. |
| `error` | `React.ReactNode` |  |  | Why the field is invalid, shown under the dropzone and read with the input. |
| `hint` | `React.ReactNode` |  |  | A line under the prompt, such as the types and the size limit. |
| `size` | `enum` | `sm`, `md`, `lg` | `md` | Dropzone padding, icon and text: sm for a side panel, lg for a page that is mostly the upload. |
| `name` | `string` |  |  | The input's name, for a form post. |
| `inputId` | `string` |  |  | The input's id, for a Label with htmlFor. |
| `formatSize` | `(bytes: number) => string` |  |  | Formats file sizes and maxSize. Default decimal units, such as 482 KB. |
| `labels` | `FileUploadLabels` |  |  | The words: prompt ({browse} marks the button), browse, list, remove, retry, uploading ({name}), done, tooLarge ({name}, {size}), wrongType ({name}, {accept}), tooMany ({max}), rejected ({name}, {error}). Defaults: Drop files here or {browse}, Browse files, Files, Remove {name}, Retry {name}, Uploading {name}, Uploaded, Larger than {size}, Not an accepted file type, You can add up to {max} files, {name}, {error}. |

## Anatomy

| Part | Description |
| --- | --- |
| `root` | The column: dropzone, error, then the file list. |
| `dropzone` | The dashed area that takes drops and opens the picker, in the sunken fill, with the control border. Hover adds the hover overlay. |
| `input` | The real input type=file, visually hidden inside the dropzone. |
| `icon` | The 16px upload icon in a raised circle. The circle is a fixed 32, 40 or 48px, as an illustration outside the control sizes. |
| `prompt` | The prompt words and the browse button. |
| `browse` | Browse files, drawn as a secondary Button. Part of the label, not a second control. |
| `hint` | The types and the limits, in secondary text. |
| `error` | Why the field is invalid, or the maxFiles notice, in the danger color. |
| `list` | The list of picked files. |
| `item` | A file tile laid out as AttachmentPreview: thumbnail, name over size, progress or error, then the mark and buttons. |
| `thumbnail` | The image or the file type icon. |
| `image` | The thumbnail of an image file. |
| `body` | The name over the size, the progress bar or the error. |
| `name` | The file name on one line, cut with an ellipsis. |
| `meta` | The file size. |
| `itemError` | Why the upload failed or why the file was turned down. |
| `done` | The success check mark of an uploaded file. |
| `retry` | The ghost icon Button that retries a failed upload. Not shown on turned-down files. |
| `remove` | The ghost icon Button that removes the file. |

## Accessibility

Role: `generic`

| Keys | Action |
| --- | --- |
| `Tab` | Moves to the file input in the dropzone, then to each file's retry and remove buttons. |
| `Enter / Space` | On the dropzone, opens the file picker. On a retry or remove button, retries or removes that file. |

- The dropzone holds a label around a real input type=file, so the picker, accept and multiple are the browser's own, and screen readers name the input by the prompt and the browse words. The hint sits outside the label, so it is read once, as the description. Enter and Space both open the picker.
- The input is described by the hint, the error and the file list.
- invalid sets aria-invalid on the input; error is shown under the dropzone in words, not by the red edge alone. A maxFiles notice is announced as an alert. Each file turned down by accept or maxSize is announced in a hidden status, EN `brief.pdf, Larger than 5 MB`.
- Each file's remove button is named with it, EN `Remove brief.pdf`; the retry button EN `Retry brief.pdf`. Both are described by the file's error.
- An uploading file has a progress bar named EN `Uploading brief.pdf`. An uploaded file has a check mark with the hidden word Uploaded.
- After a file is removed, focus goes back to the input, so it is not lost.

## Install

```bash
npx werewolf-ui add file-upload
```

Run `npx werewolf-ui init` once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See [Installation](https://www.werewolf-ui.dev/docs/installation.md) for requirements and the alternative registry setup.
