---
title: useFileUpload
description: Headless state and handlers for file selection via click or drag-and-drop, with validation and previews.
icon: upload
---

### Installation

```package-install
npx shadcn@latest add @redpanda/use-file-upload
```

`useFileUpload` is the headless engine behind the `Dropzone` component. It manages selected files, drag state, and validation errors, and hands back a set of event handlers you spread onto your own markup — so you control the UI while the hook handles size/type validation, single vs. multiple modes, object-URL previews (and their cleanup), and duplicate detection.

## Usage

```tsx showLineNumbers
import { useFileUpload } from "@/hooks/use-file-upload";
import { Button } from "@/components/redpanda-ui/button";

function Uploader() {
  const [{ files, isDragging, errors }, actions] = useFileUpload({
    accept: "image/*",
    maxSize: 5 * 1024 * 1024,
    multiple: true,
    maxFiles: 5,
  });

  return (
    <div
      onDragEnter={actions.handleDragEnter}
      onDragLeave={actions.handleDragLeave}
      onDragOver={actions.handleDragOver}
      onDrop={actions.handleDrop}
      data-dragging={isDragging}
    >
      <input {...actions.getInputProps()} className="sr-only" />
      <Button onClick={actions.openFileDialog}>Choose files</Button>

      {files.map(({ id, file, preview }) => (
        <div key={id}>
          {preview ? <img src={preview} alt={file.name} /> : null}
          <span>{file.name}</span>
          <button type="button" onClick={() => actions.removeFile(id)}>
            Remove
          </button>
        </div>
      ))}

      {errors.map((error) => (
        <p key={error} className="text-destructive">{error}</p>
      ))}
    </div>
  );
}
```

## API

### `useFileUpload(options?)`

| Option          | Type                                   | Default    | Description                                              |
| --------------- | -------------------------------------- | ---------- | -------------------------------------------------------- |
| `maxFiles`      | `number`                               | `Infinity` | Max files (multiple mode only).                          |
| `maxSize`       | `number`                               | `Infinity` | Max size per file, in bytes.                             |
| `accept`        | `string`                               | `'*'`      | Accepted types, e.g. `image/*` or `.png,.jpg`.           |
| `multiple`      | `boolean`                              | `false`    | Allow selecting more than one file.                      |
| `initialFiles`  | `FileMetadata[]`                       | `[]`       | Pre-populated files (e.g. already-uploaded assets).      |
| `onFilesChange` | `(files: FileWithPreview[]) => void`   | —          | Called whenever the file list changes.                   |
| `onFilesAdded`  | `(added: FileWithPreview[]) => void`   | —          | Called with only the newly added files.                  |

**Returns** `[state, actions]`

- `state`: `{ files, isDragging, errors }`
- `actions`: `{ addFiles, removeFile, clearFiles, clearErrors, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, handleFileChange, openFileDialog, getInputProps }`

The package also exports a `formatBytes(bytes, decimals?)` helper for rendering file sizes.

## Related

For a ready-made UI, use the [Dropzone](/docs/dropzone) component, which is built on this hook.
