<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# FileField

A labeled file input with a drop zone, on the Field contract. Files can be chosen with the system picker (or a phone's camera, with `capture`) or dropped on the zone; the chosen ones are listed with a thumbnail for images and a remove button, and they stay in the native input, so a plain form submits them. `accept`, `maxSize` and `multiple` filter what comes in; a file that is left out gets a message under the field (`rejectMessage` translates it), and `onReject` reports it.

```tsx
import { FileField } from "@/components/ouro/file-field/file-field";
```

## Example

```tsx
<FileField label="Recipe photo" name="photo" accept="image/*" maxSize={5 * 1024 * 1024} error={errors?.photo} />
<FileField label="Attachments" name="attachments" multiple maxSize={10 * 1024 * 1024} onReject={(rejected) => logRejected(rejected)} />
```

## Use instead when

- Use `TextField type="url"` instead to take a link to a file, and an upload service's own widget when files go straight to storage without a form.

## Props

Type: `FileFieldProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `accept` | `string` |  | The files it takes, as the input's `accept`: extensions and MIME types, comma-separated (`".pdf,image/*"`). Files that do not match are left out, with a message under the field, and reported to `onReject`. |
| `capture` | `"user" \| "environment"` |  | Which camera a phone opens instead of the file picker: `environment` (back) or `user` (front). Pass it with `accept="image/*"`. |
| `className` | `string` |  | Layout only: margins, width, grid placement (`mt-4`). Appearance classes warn in development; style fields in your theme. |
| `description` | `ReactNode` |  | Supporting text under the label ("PNG or JPG, up to 5 MB"), read through `aria-describedby`. |
| `disabled` | `boolean` |  | Disables the input and the drop zone, and fades the field. |
| `error` | `ReadonlyErrorMessages` |  | What is wrong, from your server action or client check. It shows under the field, and the input gets `aria-invalid`. A message is text or a React node (one with a link); an empty string or array means valid. |
| `hideLabel` | `boolean` |  | Hides the label visually; screen readers still read it. |
| `label` (required) | `string` |  | What the files are for ("Recipe photo"), shown above the drop zone and read as the input's name. Required: every control needs a name. To hide it visually, keep it and add `hideLabel`. |
| `maxSize` | `number` |  | The largest file it takes, in bytes: `5 * 1024 * 1024` for 5 MB. Larger files are left out, with a message under the field, and reported to `onReject`. |
| `multiple` | `boolean` |  | Takes several files; each new choice or drop adds to the list. Without it, a new file replaces the old one. |
| `name` | `string` |  | The form field name the files submit under. |
| `onFilesChange` | `((files: File[]) => void)` |  | Called with the chosen files whenever the list changes: a choice, a drop, or a removal. |
| `onReject` | `((rejections: FileRejection[]) => void)` |  | Called with the files that were left out, and why. The field already says why under itself; use this to log or count them. |
| `prompt` | `string` | `"Drag a file here, or choose one"` ("Drag files here, or choose them" with `multiple`) | The text in the drop zone. Translate it with your app. |
| `ref` | `Ref<HTMLInputElement>` |  | The file input element. |
| `rejectMessage` | `((rejection: FileRejection) => string)` | (rejection) => "notes.heic is not an accepted file type.", "big.png is larger than 5 MB." or "Only one file can be added, so b.png was left out." | The message under the field for each file that was left out, until the next choice takes every file, a removal or a form reset. It is announced to screen readers as a status, and shows next to `error` and a Form's error for this name, never instead of them. Translate it with your app. |
| `removeLabel` | `string` | `"Remove"` | The start of each file's remove button name, followed by the file name, as visually hidden text and not an `aria-label`, so a test's `getByLabel("Photo")` finds the field and not "Remove photo.jpg". Translate it with your app. |
| `required` | `boolean` |  | A file must be chosen before the form submits. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `acceptedFileTypes`: Not a prop. Ouro UI uses `accept`, as the input does: `<FileField accept="image/*,.pdf">`.
- `errorMessage`: Not a prop. Ouro UI uses `error`: `<FileField error="Choose a photo.">`.
- `helperText`: Not a prop. Ouro UI uses `description`: `<FileField description="...">`.
- `invalid`: Not a prop. `error` marks the field invalid: `<FileField error="What is wrong">`.
- `isDisabled`: Not a prop. Ouro UI uses `disabled`: `<FileField disabled>`.
- `isInvalid`: Not a prop. `error` marks the field invalid: `<FileField error="What is wrong">`.
- `isRequired`: Not a prop. Ouro UI uses `required`: `<FileField required>`.
- `maxFileSize`: Not a prop. Ouro UI uses `maxSize`, in bytes: `<FileField maxSize={5 * 1024 * 1024}>`.
- `onChange`: Not a prop. Ouro UI uses `onFilesChange`, which gets the files: `<FileField onFilesChange={(files) => ...}>`.
- `onDrop`: Not a prop. Dropped files arrive in `onFilesChange`: `<FileField onFilesChange={(files) => ...}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: FileField merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `ErrorMessage`

One error message: text, or a React element for a message that carries a link or other markup.

```ts
type ErrorMessage = string | ReactElement;
```

### `FileRejection`

A file FileField left out, and why: it did not match `accept`, was larger than `maxSize`, or was one too many without `multiple`.

```ts
interface FileRejection {
  /** The file that was left out. */
  file: File;
  /** `type`: it does not match `accept`. `size`: it is larger than `maxSize`. `count`: the field takes one file. */
  reason: "type" | "size" | "count";
}
```

### `ReadonlyErrorMessages`

A read-only message view accepted by fields and Form; mutable input arrays remain valid.

```ts
type ReadonlyErrorMessages = ErrorMessage | readonly ErrorMessage[];
```

[^layout-classes]: test:registry/ouro/components/ouro/file-field/file-field.test.tsx::FileField > forwards its ref to the input and lets layout classes win on the root verify:testExists(registry/ouro/components/ouro/file-field/file-field.test.tsx::FileField > forwards its ref to the input and lets layout classes win on the root)
