---
id: "packages/active-ui/input-otp"
title: "InputOTP"
description: "A one-time code, typed or pasted into separate slots, with a pattern that refuses the wrong characters."
url: "/packages/active-ui/input-otp"
locale: "en"
revision: "03aa665b5b135a4f"
---

# InputOTP

`InputOTP` takes a short code, such as the six digits of a verification message, and shows each
character in a slot of its own. Underneath is one real input: typing, deleting, pasting the whole
code and the browser's one-time-code autofill all work as in any text field. It is built on the
[input-otp](https://www.npmjs.com/package/input-otp) library.

**Example: A six-digit code**

Client: Runs entirely in your browser. Source: `examples/ui/input-otp.tsx`.

```tsx
// apps/docs/examples/ui/input-otp.tsx
'use client';

import {useState} from 'react';

import {
  Field,
  FieldDescription,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSeparator,
  InputOTPSlot,
  REGEXP_ONLY_DIGITS,
} from '@workspace/active-ui/components/primitives/input-otp';

/**
 * A six-digit code in two groups of three. `REGEXP_ONLY_DIGITS` refuses
 * anything but digits, typed or pasted; `onComplete` fires when the last
 * slot is filled. The code is checked here against a fixed value: a real
 * application sends it to its server.
 */
export default function InputOtpExample() {
  const [code, setCode] = useState('');
  const [result, setResult] = useState<'valid' | 'invalid' | null>(null);
  return (
    <Field className="w-fit">
      <FieldLabel htmlFor="otp-code">Verification code</FieldLabel>
      <InputOTP
        id="otp-code"
        maxLength={6}
        pattern={REGEXP_ONLY_DIGITS}
        value={code}
        onChange={(value) => {
          setCode(value);
          setResult(null);
        }}
        onComplete={(value: string) =>
          setResult(value === '123456' ? 'valid' : 'invalid')
        }
        aria-describedby="otp-code-status"
      >
        <InputOTPGroup>
          <InputOTPSlot index={0} />
          <InputOTPSlot index={1} />
          <InputOTPSlot index={2} />
        </InputOTPGroup>
        <InputOTPSeparator />
        <InputOTPGroup>
          <InputOTPSlot index={3} />
          <InputOTPSlot index={4} />
          <InputOTPSlot index={5} />
        </InputOTPGroup>
      </InputOTP>
      <FieldDescription id="otp-code-status" aria-live="polite">
        {result === 'valid'
          ? 'Code accepted.'
          : result === 'invalid'
            ? 'That code is not valid. Try 123456 in this example.'
            : 'Enter the six digits from the message.'}
      </FieldDescription>
    </Field>
  );
}
```

## Usage

```tsx
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
  REGEXP_ONLY_DIGITS,
} from '@workspace/active-ui/components/primitives/input-otp';

<InputOTP
  maxLength={6}
  pattern={REGEXP_ONLY_DIGITS}
  value={code}
  onChange={setCode}
>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    {/* … one slot per character, up to index 5 */}
  </InputOTPGroup>
</InputOTP>;
```

## Anatomy

- `InputOTP`: the input and its state. `maxLength` is the length of the code; `value` and
  `onChange` make it controlled; `onComplete` is called with the code when the last character is
  entered.
- `InputOTPGroup`: slots that touch, with joined borders.
- `InputOTPSlot`: one character, by its `index` from `0`. It shows the caret when it is the active
  slot. Render exactly `maxLength` slots.
- `InputOTPSeparator`: a dash between groups, `role="separator"`.

## A pattern that refuses

`pattern` is tested against the whole value on every change; a character that does not match is
not accepted. Pass `REGEXP_ONLY_DIGITS` for a numeric code. Write your own pattern anchored
(`^[A-Z0-9]+$`): an unanchored pattern such as `[0-9]*` matches the empty string and so refuses
nothing.

## What your application provides

- A label: an `id` on `InputOTP` with a `FieldLabel`, or `aria-label`.
- The verification. `onComplete` only reports a complete code; checking it is a server matter.
- Messages for a wrong or expired code, announced with a live region or a `FieldError`.

## Accessibility and keyboard

- One input: `Tab` reaches it once, the arrow keys move within the code, `Backspace` deletes.
- Pasting a whole code fills every slot.
- `autoComplete="one-time-code"` is set by the library, so phones can offer the code from a
  message.

## API

**InputOTP**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `containerClassName` | `string` | no | Classes for the element around the slots; `className` styles the hidden input. |

**InputOTPGroup**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

InputOTPGroup has no props of its own.

**InputOTPSlot**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `index` | `number` | yes | The position of the character this slot shows, from `0`. |

**InputOTPSeparator**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

InputOTPSeparator has no props of its own.

**REGEXP_ONLY_DIGITS**: `"^\\d+$"`

The digit pattern of `input-otp` (`^\d+$`), for `pattern` on a code field
that accepts only digits.

`input-otp` tests `new RegExp(pattern).test(value)`. An unanchored pattern
such as `[0-9]*` matches the empty string and so rejects nothing: letters
would fill the slots. This constant is anchored and rejects them.

It is exported here so that a code field imports it from this package, not
from `input-otp`, which is a dependency of this package and not of yours.

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Input OTP documentation of shadcn/ui (MIT licence) to
> this package; see [Third-party notices](/third-party-notices).
