<!-- Protocore Design System — FileUpload -->
# FileUpload

- **Category:** Inputs (`inputs`)
- **Slug:** `inputs/file-upload`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { FileUpload } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/inputs/file-upload

> A keyboard-operable dashed dropzone plus a removable selected-file list — selection only; the consumer handles the upload.

## When to use it

Use **FileUpload** anywhere a user attaches local files — evidence, logs, images, documents. It is deliberately **network-free**: it captures the selection and hands you `File` objects, leaving progress, retries, and transport to your own uploader. For a single value that's part of a larger form, wrap it in a **Field** for the label and hint.

## Props

_No documented props._

## Examples

### Basics

Click or drop onto the zone to pick a single file. `onFilesChange` reports the current `File[]`; the component only manages selection — you run the actual upload. `accept` filters the native picker.

```tsx
import { useState } from "react";
import { FileUpload, Text } from "@protocore/pds";

export default function Demo() {
  const [files, setFiles] = useState<File[]>([]);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 10, maxWidth: 420 }}>
      <FileUpload
        accept=".pdf,.png,.jpg"
        hint="PDF, PNG or JPG — drop a file or click to browse"
        onFilesChange={setFiles}
        aria-label="Upload evidence"
      />
      <Text as="span" size="sm" mono color="muted">
        {files.length === 0 ? "No file selected" : `Ready: ${files[0]?.name}`}
      </Text>
    </div>
  );
}
```

### Multiple with a size cap

Set `multiple` to accumulate files and `maxSize` (bytes) to reject oversized ones — rejections are announced. Each selected file lists as a row with a mono name, a formatted size, and an × remove.

```tsx
import { useState } from "react";
import { FileUpload, Text } from "@protocore/pds";

const MB = 1024 * 1024;

export default function Demo() {
  const [files, setFiles] = useState<File[]>([]);
  const total = files.reduce((sum, f) => sum + f.size, 0);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 10, maxWidth: 460 }}>
      <FileUpload
        multiple
        accept="image/*,.log,.txt"
        maxSize={2 * MB}
        label="ATTACH LOGS"
        hint="Images or logs, up to 2 MB each. Drop several at once."
        onFilesChange={setFiles}
        aria-label="Attach diagnostic logs"
      />
      <Text as="span" size="sm" mono color="muted">
        {files.length} attached · {(total / MB).toFixed(1)} MB total
      </Text>
    </div>
  );
}
```

## Do & don't

**Do**

- Describe accepted formats and limits in `hint` — it's wired as the zone's `aria-describedby`.
- Set `accept` and `maxSize` so bad files are filtered before they reach your uploader.
- Use `multiple` only when several files are genuinely expected.
- Drive your upload from `onFilesChange`, keeping progress state in your own layer.

**Don't**

- Don't expect the component to upload — it never touches the network.
- Don't rely on `accept` alone for validation; re-check size and type server-side.
- Don't hide the file list — users need to confirm and remove what they picked.
- Don't omit an `aria-label`; the zone is a button and needs an accessible name.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Focus the drop zone (a `role="button"`). |
| `Enter / Space` | Open the native file picker. |
| `Tab (in list)` | Reach each file's × remove button. |

**Notes**

- The zone is a labelled `role="button"` described by its `hint`; the real file input is kept out of the button and off the tab order to avoid nested interactives.
- Drag-over switches the border to the accent color via `data-dragover`; selection and rejection are announced through a polite live region.
- Each selected file exposes a `Remove <name>` button.

## Related

`input`, `field`, `button`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
