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

# ImageField

A field for one photo, shown large: the photo already saved (`src`) or the one just chosen, with Choose photo (Change photo once one shows) and Remove. With `crop`, a chosen photo opens the cropper first, and the cropped image is what shows, what `onFileChange` gets and what the form submits. A file of the wrong type, over `maxSize` or that the browser cannot open as a photo is turned away, keeping what showed, with a message under the field that screen readers announce. The photo is in a real file input, so a plain form submits it.

```tsx
import { ImageField } from "@/components/ouro/image-field/image-field";
```

## Example

```tsx
<ImageField label="Profile photo" name="photo" src={user.photoUrl} shape="circle" crop maxSize={5 * 1024 * 1024} onRemove={removePhoto} />
```

## Usage

A photo dragged onto the field lights its frame and says where to drop it; dropped, it goes through the same checks, cropper and messages as one chosen with the button, which stays the way to choose from the keyboard or a screen reader.

## Use instead when

- Use `FileField` instead for other files or several files, and `ImageCropper` alone to crop an image you already have.

## Props

Type: `ImageFieldProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `accept` | `string` | `"image/*"` | The types it takes, as the file input's `accept`. |
| `alt` | `string` | `""` (the label names it) | The photo's text alternative while it shows `src`. |
| `aspect` | `number` | `1` | The preview's width divided by its height, and the crop's. |
| `changeLabel` | `string` | `"Change photo"` | The choose button's label while a photo shows. |
| `chooseLabel` | `string` | `"Choose photo"` | The choose button's label while no photo shows. |
| `className` | `string` |  | Layout only: margins, width (`mt-6`). Appearance classes warn in development. |
| `crop` | `boolean \| ImageFieldCrop` |  | Opens the cropper on every chosen photo, so it fits `aspect` and `shape`: `crop` or `crop={{ outputWidth: 512 }}`. |
| `description` | `ReactNode` |  | Supporting text under the label, such as the formats and size it takes. |
| `disabled` | `boolean` |  | Turns the field off. |
| `dropHint` | `string` | `"or drop a photo here"` | The words beside the choose button that say a photo can be dropped on the field, shown while it is enabled. An empty string leaves them out. |
| `dropLabel` | `string` | `"Drop the photo here"` | What the photo's frame says while a file is dragged over the field. |
| `error` | `ReadonlyErrorMessages` |  | What is wrong, shown under the field in the destructive tone. |
| `hideLabel` | `boolean` |  | Keeps the label for screen readers only. |
| `label` (required) | `string` |  | What the image is, above it and naming the field: "Profile photo". |
| `maxSize` | `number` |  | The largest file it takes, in bytes. |
| `name` | `string` |  | The form field name the chosen (and cropped) photo submits under. |
| `onFileChange` | `((file: File \| null) => void)` |  | Called with the chosen photo (cropped, with `crop`), or `null` when it is removed. A photo comes here once the browser has opened it. |
| `onRemove` | `(() => void)` |  | Shows Remove while the field shows `src`, and is called when the person removes it. |
| `ref` | `Ref<HTMLInputElement>` |  | Reaches the file input. |
| `rejectMessage` | `((reason: ImageFieldRejection, file: File) => string)` |  | The message for a file turned away, for apps in other languages. |
| `removeLabel` | `string` | `"Remove"` | The remove button's label. |
| `required` | `boolean` |  | Marks the field as required: a mark after the label. Check that a photo was chosen when the form submits. |
| `shape` | `"circle" \| "rect"` | `"rect"` | `circle` shows and crops a round photo, for a profile; `rect` a rounded rectangle. |
| `src` | `string \| null` |  | The photo the field shows until another is chosen, such as the one already saved. |

## 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.

- `cropper`: Not a prop. Ouro UI says `crop`: `<ImageField crop />`.
- `defaultValue`: Not a prop. The photo shown is `src`: `<ImageField src={user.photoUrl} />`.
- `maxFileSize`: Not a prop. Ouro UI says `maxSize`, in bytes: `<ImageField maxSize={5 * 1024 * 1024} />`.
- `onChange`: Not a prop. Ouro UI says `onFileChange`: `<ImageField onFileChange={setPhoto} />`.
- `value`: Not a prop. The photo shown is `src`: `<ImageField src={user.photoUrl} />`.

## `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: ImageField 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;
```

### `ImageFieldCrop`

How `ImageField` crops a chosen photo.

```ts
interface ImageFieldCrop {
  /** The saved image's width in pixels. @default 1024 */
  outputWidth?: number | undefined;
  /** The saved image's format. @default "image/jpeg" */
  type?: "image/jpeg" | "image/png" | "image/webp" | undefined;
  /** The cropper's title. @default "Crop photo" */
  title?: string | undefined;
}
```

### `ImageFieldRejection`

Why a chosen file was turned away: not a type `accept` takes, over `maxSize`, or a photo the browser cannot open (such as HEIC outside Safari).

```ts
type ImageFieldRejection = "type" | "size" | "unreadable";
```

### `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/image-field/image-field.test.tsx::ImageField > forwards a ref to the file input, as a function or an object, and lets layout classes win verify:testExists(registry/ouro/components/ouro/image-field/image-field.test.tsx::ImageField > forwards a ref to the file input, as a function or an object, and lets layout classes win)
