# OTP Field

A one-time code with a box for each character.

Source: [otp-field.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/otp-field.tsx)

```tsx
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";
import { OTPField, OTPFieldInput } from "@/components/ui/otp-field";

export default function OTPFieldBasic() {
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="verification-code">Verification code</FieldLabel>
      <OTPField id="verification-code" length={6}>
        {Array.from({ length: 6 }, (_, index) => (
          <OTPFieldInput key={index} />
        ))}
      </OTPField>
      <FieldDescription>Enter the 6-digit code we sent to ada@example.com.</FieldDescription>
    </Field>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/otp-field
```

## Usage

```tsx
import { Field, FieldLabel } from "@/components/ui/field";
import { OTPField, OTPFieldInput } from "@/components/ui/otp-field";

export function CodeField() {
  return (
    <Field>
      <FieldLabel htmlFor="code">Code</FieldLabel>
      <OTPField id="code" length={4}>
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
      </OTPField>
    </Field>
  );
}
```

Render one `OTPFieldInput` per character, matching `length`. An `id` on `OTPField` goes to the
first box, so a label can point at it.

## Composition

- `OTPField`
  - `OTPFieldInput`
  - `OTPFieldGroup`
  - `OTPFieldSeparator`

## Examples

### Checking the code

`onValueComplete` runs once every box is filled. Set `invalid` on the `Field` and `aria-invalid`
on the inputs, and show a `FieldError` when the code is wrong.

```tsx
import * as React from "react";

import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { OTPField, OTPFieldInput } from "@/components/ui/otp-field";
import { toast } from "@/components/ui/toast";

export default function OTPFieldVerify() {
  const [code, setCode] = React.useState("");
  const [invalid, setInvalid] = React.useState(false);

  return (
    <Field invalid={invalid} className="w-fit">
      <FieldLabel htmlFor="sign-in-code">Sign-in code</FieldLabel>
      <OTPField
        id="sign-in-code"
        length={6}
        value={code}
        onValueChange={(value) => {
          setCode(value);
          setInvalid(false);
        }}
        onValueComplete={(value) => {
          if (value === "424242") {
            setCode("");
            toast.add({ title: "Code verified", type: "success" });
          } else {
            setInvalid(true);
          }
        }}
      >
        {Array.from({ length: 6 }, (_, index) => (
          <OTPFieldInput key={index} aria-invalid={invalid || undefined} />
        ))}
      </OTPField>
      {invalid ? (
        <FieldError>That code didn&apos;t work. Check it and try again.</FieldError>
      ) : (
        <FieldDescription>Try 424242, or any other code to see an error.</FieldDescription>
      )}
    </Field>
  );
}
```

### Groups

Split a long code into `OTPFieldGroup`s with an `OTPFieldSeparator` between them.

```tsx
import { Field, FieldLabel } from "@/components/ui/field";
import {
  OTPField,
  OTPFieldGroup,
  OTPFieldInput,
  OTPFieldSeparator,
} from "@/components/ui/otp-field";

export default function OTPFieldGrouped() {
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="authenticator-code">Authenticator code</FieldLabel>
      <OTPField id="authenticator-code" length={6}>
        <OTPFieldGroup>
          <OTPFieldInput />
          <OTPFieldInput />
          <OTPFieldInput />
        </OTPFieldGroup>
        <OTPFieldSeparator />
        <OTPFieldGroup>
          <OTPFieldInput />
          <OTPFieldInput />
          <OTPFieldInput />
        </OTPFieldGroup>
      </OTPField>
    </Field>
  );
}
```

### Large

```tsx
import { Field, FieldLabel } from "@/components/ui/field";
import {
  OTPField,
  OTPFieldGroup,
  OTPFieldInput,
  OTPFieldSeparator,
} from "@/components/ui/otp-field";

export default function OTPFieldLarge() {
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="device-code">Device code</FieldLabel>
      <OTPField id="device-code" length={6} size="lg">
        <OTPFieldGroup>
          <OTPFieldInput />
          <OTPFieldInput />
          <OTPFieldInput />
        </OTPFieldGroup>
        <OTPFieldSeparator />
        <OTPFieldGroup>
          <OTPFieldInput />
          <OTPFieldInput />
          <OTPFieldInput />
        </OTPFieldGroup>
      </OTPField>
    </Field>
  );
}
```

### Letters and numbers

The field accepts only digits by default. Set `validationType="alphanumeric"` to allow letters, and
`normalizeValue` to transform input, such as to uppercase.

```tsx
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field";
import {
  OTPField,
  OTPFieldGroup,
  OTPFieldInput,
  OTPFieldSeparator,
} from "@/components/ui/otp-field";

export default function OTPFieldAlphanumeric() {
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="recovery-code">Recovery code</FieldLabel>
      <OTPField
        id="recovery-code"
        length={8}
        validationType="alphanumeric"
        normalizeValue={(value) => value.toUpperCase()}
      >
        <OTPFieldGroup>
          {Array.from({ length: 4 }, (_, index) => (
            <OTPFieldInput key={index} />
          ))}
        </OTPFieldGroup>
        <OTPFieldSeparator />
        <OTPFieldGroup>
          {Array.from({ length: 4 }, (_, index) => (
            <OTPFieldInput key={index} />
          ))}
        </OTPFieldGroup>
      </OTPField>
      <FieldDescription>Letters are converted to uppercase.</FieldDescription>
    </Field>
  );
}
```

### Masked

`mask` hides the characters, like a password field.

```tsx
import { Field, FieldLabel } from "@/components/ui/field";
import { OTPField, OTPFieldInput } from "@/components/ui/otp-field";

export default function OTPFieldMasked() {
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="pin">PIN</FieldLabel>
      <OTPField id="pin" length={4} mask>
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
      </OTPField>
    </Field>
  );
}
```

### Disabled

```tsx
import { Field, FieldLabel } from "@/components/ui/field";
import { OTPField, OTPFieldInput } from "@/components/ui/otp-field";

export default function OTPFieldDisabled() {
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="previous-pin">Previous PIN</FieldLabel>
      <OTPField id="previous-pin" length={4} defaultValue="4821" mask disabled>
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
      </OTPField>
    </Field>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `OTPField` | [`OTPField.Root`](https://base-ui.com/react/components/otp-field#root) |
| `OTPFieldGroup` | `<div>` |
| `OTPFieldInput` | [`OTPField.Input`](https://base-ui.com/react/components/otp-field#input) |
| `OTPFieldSeparator` | [`OTPField.Separator`](https://base-ui.com/react/components/otp-field#separator) |

The tables leave out `className`, `style` and `render`. Parts built on Base UI take them as its [styling](https://base-ui.com/react/handbook/styling) and [composition](https://base-ui.com/react/handbook/composition) guides describe.

### OTPField

| Prop               | Type                                                                                  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------ | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id?`              | `string`                                                                              | The id of the first input element. Subsequent inputs derive their ids from it (`{id}-2`, `{id}-3`, and so on).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `autoComplete?`    | `string`                                                                              | The input autocomplete attribute. Applied to the first slot and hidden validation input. Default: `'one-time-code'`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `form?`            | `string`                                                                              | A string specifying the `form` element with which the hidden input is associated. This string's value must match the id of a `form` element in the same document.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `length`           | `number`                                                                              | The number of OTP input slots. Required so the root can clamp values, detect completion, and generate consistent validation markup before all slots hydrate.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `autoSubmit?`      | `boolean`                                                                             | Whether to submit the owning form when the OTP becomes complete. Default: `false`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `mask?`            | `boolean`                                                                             | Whether the slot inputs should mask entered characters. Pass `type` directly to individual `<OTPField.Input>` parts to use a custom input type. Default: `false`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `inputMode?`       | `"decimal" \| "email" \| "none" \| "numeric" \| "search" \| "tel" \| "text" \| "url"` | The virtual keyboard hint applied to the slot inputs and hidden validation input. Built-in validation modes provide sensible defaults, but you can override them when needed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `validationType?`  | `"alpha" \| "alphanumeric" \| "none" \| "numeric"`                                    | The type of input validation to apply to the OTP value. Default: `'numeric'`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `normalizeValue?`  | `((value: string) => string)`                                                         | Function that normalizes the OTP value after whitespace and `validationType` filtering. It runs whenever OTP Field normalizes a value, including initial/default values, controlled values, and user edits. The returned value is filtered by `validationType` again, then clamped to `length`. It should be idempotent because OTP Field may normalize the same value more than once while handling edits, storing state, and rendering controlled or uncontrolled values. Non-idempotent normalizers can compound across those normalization passes. Characters rejected while normalizing typed or pasted text are reported through `onValueInvalid`. |
| `required?`        | `boolean`                                                                             | Whether the user must enter a value before submitting a form. Default: `false`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `disabled?`        | `boolean`                                                                             | Whether the component should ignore user interaction. Default: `false`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `readOnly?`        | `boolean`                                                                             | Whether the user should be unable to change the field value. Default: `false`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `name?`            | `string`                                                                              | Identifies the field when a form is submitted.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `value?`           | `string`                                                                              | The OTP value.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `defaultValue?`    | `string`                                                                              | The uncontrolled OTP value when the component is initially rendered.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `onValueChange?`   | `((value: string, eventDetails: OTPField.Root.ChangeEventDetails) => void)`           | Callback fired when the OTP value changes. The `eventDetails.reason` indicates what triggered the change: - `'input-change'` for typing or autofill - `'input-clear'` when a character is removed by text input - `'input-paste'` for paste interactions - `'keyboard'` for keyboard interactions that change the value                                                                                                                                                                                                                                                                                                                                  |
| `onValueInvalid?`  | `((value: string, eventDetails: OTPField.Root.InvalidEventDetails) => void)`          | Callback fired when entered text contains characters that are rejected by validation or normalization before the OTP value updates. The `value` argument is the attempted user-entered string before normalization.                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `onValueComplete?` | `((value: string, eventDetails: OTPField.Root.CompleteEventDetails) => void)`         | Callback function that is fired when the OTP value becomes complete, or when a complete value is pasted while the OTP is already complete. When the value changes, it runs later than `onValueChange`, after the internal value update is applied. If a complete pasted value matches the current value, `onValueChange` does not fire. If `autoSubmit` is enabled, it runs immediately before the owning form is submitted.                                                                                                                                                                                                                             |
| `size?`            | `"default" \| "lg"`                                                                   | Default: `"default"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |

### OTPFieldSeparator

| Prop           | Type                         | Description                                               |
| -------------- | ---------------------------- | --------------------------------------------------------- |
| `orientation?` | `"horizontal" \| "vertical"` | The orientation of the separator. Default: `'horizontal'` |
