InputOTP
A one-time password input component with auto-focus and backspace navigation.
Enter verification code
We sent a code to your email
<InputOTP length={6} value={otp} onValueChange={setOtp} />Installation
npx @aniui/cli add input-otpUsage
app/index.tsx
import { InputOTP } from "@/components/ui/input-otp";
import { useState } from "react";
export function MyScreen() {
const [otp, setOtp] = useState("");
return (
<InputOTP
length={6}
value={otp}
onValueChange={setOtp}
/>
);
}Examples
6-digit verification code
<InputOTP length={6} value={otp} onValueChange={setOtp} />4-digit PIN
<InputOTP length={4} value={pin} onValueChange={setPin} />Props
PropTypeDefault
lengthnumber6valuestring""onValueChange(value: string) => void—classNamestring—Also accepts all View props from React Native.
Source
components/ui/input-otp.tsx
import React, { useRef, useState } from "react";
import { View, TextInput } from "react-native";
import { cn } from "@/lib/utils";
export interface InputOTPProps extends React.ComponentPropsWithoutRef<typeof View> {
className?: string;
length?: number;
value?: string;
onValueChange?: (value: string) => void;
}
export function InputOTP({ length = 6, value = "", onValueChange, className, ...props }: InputOTPProps) {
const refs = useRef<(TextInput | null)[]>([]);
const [focused, setFocused] = useState(0);
const digits = value.padEnd(length, "").slice(0, length).split("");
const handleChange = (text: string, index: number) => {
const char = text.slice(-1);
const next = [...digits];
next[index] = char;
onValueChange?.(next.join(""));
if (char && index < length - 1) {
refs.current[index + 1]?.focus();
}
};
const handleKeyPress = (key: string, index: number) => {
if (key === "Backspace" && !digits[index] && index > 0) {
refs.current[index - 1]?.focus();
}
};
return (
<View className={cn("flex-row gap-2", className)} accessibilityRole="none" {...props}>
{digits.map((digit, i) => (
<TextInput
key={i}
ref={(el) => { refs.current[i] = el; }}
className={cn(
"h-12 w-10 rounded-md border text-center text-lg font-semibold text-foreground bg-background",
focused === i ? "border-ring" : "border-input"
)}
value={digit}
onChangeText={(t) => handleChange(t, i)}
onKeyPress={({ nativeEvent }) => handleKeyPress(nativeEvent.key, i)}
onFocus={() => setFocused(i)}
keyboardType="number-pad"
maxLength={1}
selectTextOnFocus
accessible={true}
accessibilityLabel={`Digit ${i + 1} of ${length}`}
/>
))}
</View>
);
}