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

# OtpField

A one-time code entry on the Field contract: one slot per character, with focus that moves as the person types, a whole code pasted at once, and `autoComplete="one-time-code"` so phones offer the code from a text message. Pass `label`, `length`, and `error` from your server action; `onValueComplete` or `autoSubmit` act when every slot is filled.

```tsx
import { OtpField } from "@/components/ouro/otp-field/otp-field";
```

## Example

```tsx
<OtpField label="Verification code" name="code" length={6} autoSubmit error={errors?.code} />
<OtpField label="Recovery code" validationType="alphanumeric" length={8} groupSize={4} />
```

## Use instead when

- Use `TextField` with `inputMode="numeric"` instead for a number with no fixed length, and `TextField type="password"` for a password.

## Props

Type: `OtpFieldProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `autoComplete` | `string` | `"one-time-code"` | Autofill for the first slot. The default lets phones offer a code from a text message. |
| `autoSubmit` | `boolean` |  | Submits the form when every slot is filled. |
| `className` | `string` |  | Layout only: margins, width, grid placement (`mt-4`). Appearance classes warn in development; style fields in your theme. |
| `defaultValue` | `string` |  | The code the field starts with, when it keeps its own state. |
| `description` | `ReactNode` |  | Supporting text under the label ("We sent it to your phone"), read through `aria-describedby`. |
| `disabled` | `boolean` |  | Disables every slot and fades the field. |
| `error` | `ReadonlyErrorMessages` |  | What is wrong ("That code has expired"), from your server action or client check. It shows under the slots, which get `aria-invalid`. A message is text or a React node (one with a link); an empty string or array means valid. |
| `groupSize` | `number` |  | Shows the slots in groups of this many, with a dash between groups: `groupSize={3}` reads `123-456`. |
| `hideLabel` | `boolean` |  | Hides the label visually; screen readers still read it. |
| `label` (required) | `string` |  | What the code is ("Verification code"), shown above the slots and read as the first slot's name. Required: every control needs a name. To hide it visually, keep it and add `hideLabel`. |
| `length` | `number` | `6` | How many characters the code has: one slot each. |
| `mask` | `boolean` |  | Hides the characters as dots, as a password field does. |
| `name` | `string` |  | The form field name the whole code submits under. |
| `onValueChange` | `((value: string) => void)` |  | Called with the code as the person types, pastes or deletes. |
| `onValueComplete` | `((value: string) => void)` |  | Called with the code when every slot is filled. |
| `readOnly` | `boolean` |  | Shows the code without letting the person change it. |
| `ref` | `Ref<HTMLDivElement>` |  | The slots' group element (`role="group"`). |
| `required` | `boolean` |  | The code must be filled before the form submits. |
| `slotLabel` | `((position: number, length: number) => string)` | (position, length) => `Character ${position} of ${length}` | The name of each slot after the first (the first takes `label`). Translate it with your app. |
| `validationType` | `"numeric" \| "alpha" \| "alphanumeric"` | `"numeric"` | Which characters the slots take. Others are dropped as the person types or pastes. `numeric` brings up the number keyboard on phones. |
| `value` | `string` |  | The code, when you control it. Pair it with `onValueChange`. |

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

- `errorMessage`: Not a prop. Ouro UI uses `error`: `<OtpField error="Wrong code.">`.
- `helperText`: Not a prop. Ouro UI uses `description`: `<OtpField description="...">`.
- `invalid`: Not a prop. `error` marks the field invalid: `<OtpField error="What is wrong">`.
- `isDisabled`: Not a prop. Ouro UI uses `disabled`: `<OtpField disabled>`.
- `isInvalid`: Not a prop. `error` marks the field invalid: `<OtpField error="What is wrong">`.
- `isRequired`: Not a prop. Ouro UI uses `required`: `<OtpField required>`.
- `maxLength`: Not a prop. Ouro UI uses `length`: `<OtpField length={6}>`.
- `numInputs`: Not a prop. Ouro UI uses `length`: `<OtpField length={4}>`.
- `onChange`: Not a prop. Ouro UI uses `onValueChange`, which gets the code: `<OtpField onValueChange={(code) => ...}>`.
- `onComplete`: Not a prop. Ouro UI uses `onValueComplete`: `<OtpField onValueComplete={(code) => ...}>`.
- `pattern`: Not a prop. Ouro UI uses `validationType`: `<OtpField validationType="alphanumeric">`.

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

### `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/otp-field/otp-field.test.tsx::OtpField > forwards its ref to the group and lets layout classes win on the root verify:testExists(registry/ouro/components/ouro/otp-field/otp-field.test.tsx::OtpField > forwards its ref to the group and lets layout classes win on the root)
