---
id: "packages/active-ui/simple-dropzone"
title: "SimpleDropzone"
description: "Choose one file with the file picker or by dropping it on a box, with the file name and a button to remove it."
url: "/packages/active-ui/simple-dropzone"
locale: "en"
revision: "29c5c564b4ef9902"
---

# SimpleDropzone

`SimpleDropzone` picks one file. The reader opens the file picker from the box, or drops a file on
it; the chosen file's name appears with a button to remove it. Your application receives the
`File` and does the rest: checking, reading, uploading. To show files that are uploading, use
[Attachment](/packages/active-ui/attachment).

**Example: One CSV file, picked or dropped**

Client: Runs entirely in your browser. Source: `examples/ui/simple-dropzone.tsx`.

```tsx
// apps/docs/examples/ui/simple-dropzone.tsx
'use client';

import {useState} from 'react';

import {formatByteSize} from '@workspace/active-format';
import {SimpleDropzone} from '@workspace/active-ui/components/composed/simple-dropzone';
import {
  Field,
  FieldDescription,
  FieldError,
} from '@workspace/active-ui/components/primitives/field';

/**
 * One file, chosen with the file picker or dropped on the box. The
 * instruction is the field's label. The check on the type is the
 * application's: `accept` only filters the picker, so a dropped file of
 * another type gets an error here. Nothing is uploaded.
 */
export default function SimpleDropzoneExample() {
  const [file, setFile] = useState<File | null>(null);
  const wrongType = file !== null && !file.name.toLowerCase().endsWith('.csv');
  return (
    <Field className="w-full max-w-md" data-invalid={wrongType || undefined}>
      <SimpleDropzone
        accept=".csv,text/csv"
        onFileChange={setFile}
        instruction="Choose a CSV file, or drop it here"
        clearFileLabel="Remove the file"
        aria-invalid={wrongType || undefined}
        aria-describedby="participants-file-state"
      />
      {wrongType ? (
        <FieldError id="participants-file-state">
          Choose a CSV file. {file.name} is not one.
        </FieldError>
      ) : (
        <FieldDescription id="participants-file-state">
          {file
            ? `${file.name}, ${formatByteSize(file.size, 'en', {system: 'decimal'})}`
            : 'A CSV file with one participant per row.'}
        </FieldDescription>
      )}
    </Field>
  );
}
```

## What the composition adds

- **The file input itself is the control.** It is visually hidden but focusable, and the
  instruction is its `<label>`: clicking the box or pressing `Enter` or `Space` on the focused field
  opens the picker. The box shows the focus ring.
- **Dropping is an addition**, never the only way in.
- **`disabled` closes every way in**: the field, the label, the remove button and dropping.
- `aria-invalid` and `aria-describedby` land on the file input, so an error is read with the field.

One file only, with no maximum size, no progress and no error message of its own.

## Usage

```tsx
import {SimpleDropzone} from '@workspace/active-ui/components/composed/simple-dropzone';

<SimpleDropzone
  accept=".csv,text/csv"
  onFileChange={setFile}
  instruction="Choose a CSV file, or drop it here"
  clearFileLabel="Remove the file"
  aria-invalid={invalid || undefined}
  aria-describedby="file-error"
/>;
```

## What your application provides

- An instruction that names both ways in, and the name of the remove button, translated.
- The check of the file. `accept` filters the picker only; a dropped file and a picker set to "all
  files" bypass it, as the example shows with a non-CSV file.
- The error below the field, and the description of what is accepted.

`id` lands on the file input and `className` on the box.

## Accessibility and keyboard

- One tab stop for the field, and one for the remove button once a file is chosen.
- The instruction names the field; a second `FieldLabel` would give it two names.

## API

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `clearFileLabel` | `string` | yes | Accessible name of the button that releases the chosen file again. |
| `instruction` | `string` | yes | Visible instruction in the box, and at the same time the accessible name of the file input: the `<label>` around it links the two.  Supplied translated and required; `active-ui` does not translate.  The text names **both** input paths. Dragging is a supplement and never the only route, so an instruction that only says "drag a file here" leaves keyboard and screen-reader users without guidance. |
| `onFileChange` | `(file: File \| null) => void` | yes | Receives the chosen or dropped file, or `null` when it is cleared. |
| `accept` | `string` | no | File types the field offers, as an `accept` value.  This is a filter in the file picker and **not validation**: the user can bypass that filter in every operating system. The substantive check stays the caller's schema. What the restriction says in human language belongs in the field's `<FieldDescription>`; this component does not write a second text over it. |
| `disabled` | `boolean` | no | Turns off the field, the clear button and dropping at once. |
| `ref` | `Ref<HTMLDivElement>` | no | The box element. |
