Implement an OTP Input component with auto-focus and backspace handling.
ReactDOMUX
Live Interactive Demo
Loading Demo...
Implementation
typescript.tsx
import React, { useState, useRef, KeyboardEvent, ChangeEvent, ClipboardEvent } from "react";
const OTPInput = ({ length = 6, onComplete = (val: string) => console.log(val) }) => {
const [otp, setOtp] = useState<string[]>(new __PH_60__(length).fill(""));
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
const focusInput = (index: number) => {
if (index >= 0 && index < length && inputRefs.current[index]) {
inputRefs.current[index]?.focus();
}
};
const handleChange = (e: ChangeEvent<HTMLInputElement>, index: number) => {
if (isNaN(Number(e.target.value))) return;
const newOtp = [...otp];
const val = e.target.value.slice(-1);
newOtp[index] = val;
setOtp(newOtp);
if (newOtp.every(v => v !== "")) onComplete(newOtp.join(""));
if (val && index < length - 1) focusInput(index + 1);
};
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>, index: number) => {
if (e.key === "Backspace") {
if (!otp[index] && index > 0) {
const newOtp = [...otp];
newOtp[index - 1] = "";
setOtp(newOtp);
focusInput(index - 1);
} else {
const newOtp = [...otp];
newOtp[index] = "";
setOtp(newOtp);
}
} else if (e.key === "ArrowLeft" && index > 0) focusInput(index - 1);
else if (e.key === "ArrowRight" && index < length - 1) focusInput(index + 1);
};
const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
e.preventDefault();
const pasteData = e.clipboardData.getData("text").slice(0, length);
if (!/^\d+$/.test(pasteData)) return;
const newOtp = [...otp];
pasteData.split("").forEach((char, i) => newOtp[i] = char);
setOtp(newOtp);
const nextIndex = pasteData.length < length ? pasteData.length : length - 1;
focusInput(nextIndex);
if (newOtp.every(v => v !== "")) onComplete(newOtp.join(""));
};
return (
<div className="flex gap-2 justify-center p-4">
{otp.map((data, index) => (
<input
key={index}
type="text"
inputMode="numeric"
ref={(el) => { inputRefs.current[index] = el; }}
value={data}
onChange={(e) => handleChange(e, index)}
onKeyDown={(e) => handleKeyDown(e, index)}
onPaste={handlePaste}
onFocus={(e) => e.target.select()}
className="w-12 h-14 text-center text-2xl font-bold border-2 rounded outline-none"
/>
))}
</div>
);
};The Core Concept
A professional OTP input requires seamless focus management. Keys include:
1. **Ref Arrays**: dynamic refs to access each input element. 2. **onChange**: moving focus to the next input when a digit is entered. 3. **onKeyDown**: handling Backspace to clear and move focus to the previous input. 4. **Paste functionality**: splitting a pasted string across all inputs.