tetra-ui Logotetra ui

OTP Input

A component for entering one-time passwords with individual character slots.

otp-input

Installation

npx shadcn@latest add @tetra-ui/otp-input

Usage

The API mirrors shadcn Input OTPInputOTP maps to OTPInput, InputOTPGroup to OTPInputGroup, and so on.

import {
  OTPInput,
  OTPInputGroup,
  OTPInputSeparator,
  OTPInputSlot,
} from "@/components/ui/otp-input";
<OTPInput maxLength={6} onComplete={(code) => console.log(code)}>
  <OTPInputGroup>
    <OTPInputSlot index={0} />
    <OTPInputSlot index={1} />
    <OTPInputSlot index={2} />
  </OTPInputGroup>
  <OTPInputSeparator />
  <OTPInputGroup>
    <OTPInputSlot index={3} />
    <OTPInputSlot index={4} />
    <OTPInputSlot index={5} />
  </OTPInputGroup>
</OTPInput>

Four digits

A common pattern for PIN codes:

<OTPInput maxLength={4} secureTextEntry onComplete={(code) => console.log(code)}>
  <OTPInputSlot index={0} />
  <OTPInputSlot index={1} />
  <OTPInputSlot index={2} />
  <OTPInputSlot index={3} />
</OTPInput>

Controlled

const [value, setValue] = useState("");

<OTPInput maxLength={6} onChange={setValue} value={value}>
  <OTPInputGroup>
    <OTPInputSlot index={0} />
    <OTPInputSlot index={1} />
    <OTPInputSlot index={2} />
  </OTPInputGroup>
  <OTPInputSeparator />
  <OTPInputGroup>
    <OTPInputSlot index={3} />
    <OTPInputSlot index={4} />
    <OTPInputSlot index={5} />
  </OTPInputGroup>
</OTPInput>

Disabled

<OTPInput disabled maxLength={6}>
  <OTPInputGroup>
    <OTPInputSlot index={0} />
    <OTPInputSlot index={1} />
    <OTPInputSlot index={2} />
  </OTPInputGroup>
  <OTPInputSeparator />
  <OTPInputGroup>
    <OTPInputSlot index={3} />
    <OTPInputSlot index={4} />
    <OTPInputSlot index={5} />
  </OTPInputGroup>
</OTPInput>

Invalid

<OTPInput invalid maxLength={6}>
  <OTPInputGroup>
    <OTPInputSlot index={0} />
    <OTPInputSlot index={1} />
    <OTPInputSlot index={2} />
  </OTPInputGroup>
  <OTPInputSeparator />
  <OTPInputGroup>
    <OTPInputSlot index={3} />
    <OTPInputSlot index={4} />
    <OTPInputSlot index={5} />
  </OTPInputGroup>
</OTPInput>

Pattern

Restrict input to specific character patterns:

import { OTPInput, REGEXP_ONLY_DIGITS } from "@/components/ui/otp-input";

<OTPInput maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
  <OTPInputGroup>
    <OTPInputSlot index={0} />
    <OTPInputSlot index={1} />
    <OTPInputSlot index={2} />
  </OTPInputGroup>
  <OTPInputSeparator />
  <OTPInputGroup>
    <OTPInputSlot index={3} />
    <OTPInputSlot index={4} />
    <OTPInputSlot index={5} />
  </OTPInputGroup>
</OTPInput>

Form

Use with Field and FieldControl for labels and validation:

import {
  Field,
  FieldControl,
  FieldErrorMessage,
  FieldLabel,
} from "@/components/ui/form";
import {
  OTPInput,
  OTPInputGroup,
  OTPInputSeparator,
  OTPInputSlot,
} from "@/components/ui/otp-input";

<Field errorMessage={error} invalid={!!error}>
  <FieldLabel>Verification code</FieldLabel>
  <FieldControl>
    <OTPInput maxLength={6} onChange={setValue} value={value}>
      <OTPInputGroup>
        <OTPInputSlot index={0} />
        <OTPInputSlot index={1} />
        <OTPInputSlot index={2} />
      </OTPInputGroup>
      <OTPInputSeparator />
      <OTPInputGroup>
        <OTPInputSlot index={3} />
        <OTPInputSlot index={4} />
        <OTPInputSlot index={5} />
      </OTPInputGroup>
    </OTPInput>
  </FieldControl>
  <FieldErrorMessage />
</Field>

API Reference

OTPInput

Root OTP field with its own prop surface (not a TextInput wrapper).

Prop

Type

OTPInputGroup

Extends React Native View props.

OTPInputSlot

Extends React Native Pressable props. Defaults children to placeholder, value, and caret.

Prop

Type

OTPInputSlotPlaceholder

Extends Text props. Falls back to placeholder[index] from OTPInput.

OTPInputSlotValue

Extends Text props. Falls back to the slot character (or when secureTextEntry).

OTPInputSlotCaret

Extends Reanimated Animated.View props. Renders only when the slot is active and empty.

OTPInputSeparator

Extends React Native View props. Must be rendered under OTPInput.

On this page