# File Upload (/docs/file-upload)





`FileUpload` is a drag-and-drop file picker. It renders a dashed dropzone with a browse button, then lists the accepted files — each with an optional image thumbnail, name, human-readable size, and a remove button. Drop, click-to-browse, keyboard, and the full ARIA model come from Ark UI's FileUpload machine. It implements the kit's value-based form-control contract — `onChange` hands you the accepted `File[]`, and `name` wires the hidden native input into form submission. Label, description, and error are built in via [`Field`](/docs/field).

```tsx
import { FileUpload } from '@42-central/ui-react/file-upload';

<FileUpload label="Submission" />
```

## Playground [#playground]

<story.Playground />

## Multiple files [#multiple-files]

`maxFiles` defaults to `1` (single-file). Raise it to accept several at once — each accepted file gets its own row.

<story.Preview variant="Multiple" />

## Restricting types [#restricting-types]

Gate the picker with `accept` — a MIME map (`{ 'image/*': ['.png', '.jpg'] }`) or a glob string (`'image/*'`). Rejected files are dropped silently. `maxFileSize` (in bytes) caps each file's size.

```tsx
<FileUpload label="Avatar" accept="image/*" />

<FileUpload
  label="Documents"
  accept={{ 'application/pdf': ['.pdf'] }}
  maxFileSize={5 * 1024 * 1024}
/>
```

<story.Preview variant="Images only" />

## Sizes & colors [#sizes--colors]

`color` accents the dropzone while a file is being dragged over it (and the browse button's outline). The dropzone is otherwise palette-independent.

<Preview align="block" code="&#x22;<FileUpload label=\&#x22;Brand\&#x22; color=\&#x22;brand\&#x22; />\n<FileUpload label=\&#x22;Green\&#x22; color=\&#x22;green\&#x22; />\n<FileUpload label=\&#x22;Purple\&#x22; color=\&#x22;purple\&#x22; />&#x22;">
  <div className="flex w-full max-w-[24rem] flex-col gap-5">
    <FileUpload label="Brand" color="brand" />

    <FileUpload label="Green" color="green" />

    <FileUpload label="Purple" color="purple" />
  </div>
</Preview>

## Label, description & error [#label-description--error]

`FileUpload` wraps itself in [`Field`](/docs/field), so `label` / `description` / `error` / `required` work out of the box and wire the `aria-*` relationships. Passing `error` flips the dropzone to the invalid (red) state.

<Preview align="block" code="&#x22;<FileUpload\n  label=\&#x22;Project files\&#x22;\n  description=\&#x22;Up to 3 files, 5 MB each.\&#x22;\n  maxFiles={3}\n/>\n\n<FileUpload label=\&#x22;CV\&#x22; error=\&#x22;A file is required\&#x22; />&#x22;">
  <div className="flex w-full max-w-[24rem] flex-col gap-5">
    <FileUpload label="Project files" description="Up to 3 files, 5 MB each." maxFiles="3" />

    <FileUpload label="CV" error="A file is required" />
  </div>
</Preview>

## Controlled [#controlled]

Drive the accepted files from state. `onChange` hands you the `File[]` whenever the selection changes.

```tsx
'use client';
import { useState } from 'react';
import { FileUpload } from '@42-central/ui-react/file-upload';

export function AvatarUpload() {
  const [files, setFiles] = useState<File[]>([]);
  return (
    <FileUpload
      label="Avatar"
      accept="image/*"
      value={files}
      onChange={setFiles}
    />
  );
}
```

## Forms [#forms]

`FileUpload` renders a hidden native `<input type="file">`, so a `name` wires it into native `<form>` submission and `FormData` — no controlled state required.

```tsx
<form action={save}>
  <FileUpload name="submission" label="Submission" />
</form>
```

## API [#api]

<TypeTable
  type="{
  value: {
    type: 'File[]',
    description: 'Controlled accepted files.',
  },
  onChange: {
    type: '(files: File[]) => void',
    description: 'Fires with the accepted files when the selection changes.',
  },
  accept: {
    type: 'Record<string, string[]> | string | string[]',
    description: 'Accepted types — a MIME map or a glob string.',
  },
  maxFiles: { type: 'number', default: '1', description: 'Max number of files.' },
  maxFileSize: { type: 'number', description: 'Max size per file, in bytes.' },
  directory: { type: 'boolean', default: 'false', description: 'Accept directories (webkit only).' },
  invalid: { type: 'boolean', default: 'false', description: &#x22;Red invalid state, OR'd with error.&#x22; },
  disabled: { type: 'boolean', default: 'false', description: 'Disable the control.' },
  size: { type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;', default: '&#x22;md&#x22;', description: 'Field typography sizing.' },
  color: { type: 'Color', default: '&#x22;brand&#x22;', description: 'Drag-over accent.' },
  label: { type: 'ReactNode', description: 'Field label.' },
  description: { type: 'ReactNode', description: 'Field helper text.' },
  error: { type: 'ReactNode', description: 'Field error; sets the invalid state.' },
  required: { type: 'boolean', default: 'false', description: 'Required flag + asterisk.' },
  hint: { type: 'ReactNode', default: '&#x22;Drag & drop files here, or&#x22;', description: 'Helper line inside the dropzone.' },
  name: { type: 'string', description: 'Name for the hidden file input (form submission).' },
  translations: {
    type: '{ deleteFile?: (file: File) => string; browseFiles?: ReactNode; dropzone?: string; itemPreview?: (file: File) => string }',
    description: &#x22;Ark's own FileUpload keys plus browseFiles: deleteFile names each file's remove button (default `Remove ${file.name}`), browseFiles is the withButton trigger's text (default \&#x22;Browse files\&#x22;), and dropzone / itemPreview keep Ark's English defaults. A partial override keeps the other defaults.&#x22;,
  },
  classNames: { type: '{ field?, root?, dropzone?, itemGroup?, item? }', description: 'Per-part class overrides.' },
  testId: { type: 'string', description: 'data-testid on the visible dropzone surface, not the hidden native <input type=&#x22;file&#x22;>. See the Testing guide.' },
}"
/>

## Accessibility [#accessibility]

* Built on Ark UI's FileUpload machine: the dropzone is a labelled button (`Enter` / `Space` opens the file dialog), and drop / drag-over states are announced.
* A hidden native `<input type="file">` backs form submission; pass `name` to include it.
* Each accepted file's remove button has an `aria-label` (`Remove <filename>`).
* `invalid` (or `error`) flips the dropzone to the red state; label / description / error associations are wired via the built-in `Field`.
* `testId` renders as `data-testid` on the visible dropzone surface — never the hidden native `<input type="file">`. See the [Testing](/docs/testing) guide.
* RTL is honored via the document `dir`.
