# Input OTP

A row of single-character boxes for entering a one-time code, such as a verification or recovery code.

```tsx
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@zenoxai/ui/components/input-otp"

function InputOTPDemo() {
  return (
    <InputOTP maxLength={6} aria-label="Verification code">
      <InputOTPGroup>
        <InputOTPSlot index={0} />
        <InputOTPSlot index={1} />
        <InputOTPSlot index={2} />
      </InputOTPGroup>
      <InputOTPSeparator />
      <InputOTPGroup>
        <InputOTPSlot index={3} />
        <InputOTPSlot index={4} />
        <InputOTPSlot index={5} />
      </InputOTPGroup>
    </InputOTP>
  )
}
```

## Usage

```tsx
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
} from "@zenoxai/ui/components/input-otp"

<InputOTP maxLength={6} aria-label="Verification code">
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>
```

Use it only for short codes the user copies from somewhere else: an email, an SMS, an
authenticator app. For anything else, use an [Input](/components/input).

`InputOTP` is built on the [input-otp](https://github.com/guilhermerodz/input-otp) package. A
single transparent `<input>` sits over the boxes and holds the whole value; the slots only draw
it. Render one `InputOTPSlot` per character, with `index` from `0` to `maxLength - 1`, and
split them into `InputOTPGroup`s with an `InputOTPSeparator` when the code reads better in
parts.

Props of the underlying `<input>` pass through: `id`, `name`, `disabled`, `autoComplete`,
`inputMode`, `aria-*`. `value` and `onChange(value)` work with the full string, and
`onComplete` fires when the last character is entered.

## Examples

### Digits only

`pattern` takes a regular expression as a string; input that does not match is ignored,
including pasted text. Pair it with `inputMode="numeric"` for the number keypad on phones and
`autoComplete="one-time-code"` so the platform can offer the code from an SMS.

```tsx
import { Field, FieldDescription, FieldLabel } from "@zenoxai/ui/components/field"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@zenoxai/ui/components/input-otp"

function InputOTPDigitsOnly() {
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="mfa-code">Authenticator code</FieldLabel>
      <InputOTP
        id="mfa-code"
        maxLength={6}
        pattern="^\d+$"
        inputMode="numeric"
        autoComplete="one-time-code"
      >
        <InputOTPGroup>
          <InputOTPSlot index={0} />
          <InputOTPSlot index={1} />
          <InputOTPSlot index={2} />
          <InputOTPSlot index={3} />
          <InputOTPSlot index={4} />
          <InputOTPSlot index={5} />
        </InputOTPGroup>
      </InputOTP>
      <FieldDescription>
        The 6-digit code from your authenticator app.
      </FieldDescription>
    </Field>
  )
}
```

### Controlled

`onChange` receives the whole code as a string. Verify it in `onComplete` or once its length
reaches `maxLength`.

```tsx
import * as React from "react"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@zenoxai/ui/components/input-otp"

function InputOTPControlled() {
  const [code, setCode] = React.useState("")

  return (
    <div className="grid justify-items-center gap-3">
      <InputOTP
        maxLength={6}
        value={code}
        onChange={setCode}
        aria-label="Recovery code"
      >
        <InputOTPGroup>
          <InputOTPSlot index={0} />
          <InputOTPSlot index={1} />
          <InputOTPSlot index={2} />
          <InputOTPSlot index={3} />
          <InputOTPSlot index={4} />
          <InputOTPSlot index={5} />
        </InputOTPGroup>
      </InputOTP>
      <p className="text-sm text-text-secondary">
        {code.length === 6
          ? `Checking ${code}...`
          : `${6 - code.length} characters left.`}
      </p>
    </div>
  )
}
```

### Invalid

Set `aria-invalid` on `InputOTP`: the input announces it and every slot shows the destructive
border. Put the message in a `FieldError`.

```tsx
import { Field, FieldError, FieldLabel } from "@zenoxai/ui/components/field"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@zenoxai/ui/components/input-otp"

function InputOTPInvalid() {
  return (
    <Field data-invalid className="w-fit">
      <FieldLabel htmlFor="otp-invalid">Verification code</FieldLabel>
      <InputOTP
        id="otp-invalid"
        maxLength={6}
        defaultValue="482913"
        aria-invalid
        aria-describedby="otp-invalid-error"
      >
        <InputOTPGroup>
          <InputOTPSlot index={0} />
          <InputOTPSlot index={1} />
          <InputOTPSlot index={2} />
          <InputOTPSlot index={3} />
          <InputOTPSlot index={4} />
          <InputOTPSlot index={5} />
        </InputOTPGroup>
      </InputOTP>
      <FieldError id="otp-invalid-error">
        This code has expired. Request a new one.
      </FieldError>
    </Field>
  )
}
```

### Disabled

```tsx
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@zenoxai/ui/components/input-otp"

function InputOTPDisabled() {
  return (
    <InputOTP
      maxLength={6}
      defaultValue="1234"
      disabled
      aria-label="Verification code"
    >
      <InputOTPGroup>
        <InputOTPSlot index={0} />
        <InputOTPSlot index={1} />
        <InputOTPSlot index={2} />
        <InputOTPSlot index={3} />
        <InputOTPSlot index={4} />
        <InputOTPSlot index={5} />
      </InputOTPGroup>
    </InputOTP>
  )
}
```

## Accessibility

- Screen readers see one text input, not six boxes. Name it with `aria-label` or a
  `FieldLabel` pointing at its `id`.
- Paste fills every slot at once, and password managers can fill the code into the hidden
  input.
- Say in the label or description how many characters the code has and where to find it.

## API reference

`import { ... } from "@zenoxai/ui/components/input-otp"`

### InputOTP

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| containerClassName | `string` |  | Classes for the wrapper around the slots; `className` styles the input. |

### InputOTPGroup

No props of its own.

### InputOTPSlot

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| index (required) | `number` |  | Position of the character it shows, from 0. |

### InputOTPSeparator

No props of its own.

