react ~20 KB 0 deps v1.2.0 ↗ GitHub ↗

@mshafiqyajid/react-otp-input

Headless OTP / verification-code input hook and styled component for React. Smart paste fills all slots. Full keyboard nav. Masking, group separators, label/hint/error states, and SMS autofill via autoComplete=one-time-code. Form-input contract: name + hidden input that mirrors the live OTP value.

Playground #

Verification code

Type or paste to auto-fill

Props
TSX
import { OTPInputStyled } from "@mshafiqyajid/react-otp-input/styled";
import "@mshafiqyajid/react-otp-input/styles.css";

<OTPInputStyled
  onComplete={handleComplete}
/>

useOTPResend demo

15-second cooldown. Click Resend code to trigger a mock async send.

Verification code

Enter the code from your email

Install #

npm install @mshafiqyajid/react-otp-input

Quick start #

import { OTPInputStyled } from "@mshafiqyajid/react-otp-input/styled";
import "@mshafiqyajid/react-otp-input/styles.css";

<OTPInputStyled
  length={6}
  tone="primary"
  onComplete={(code) => verify(code)}
  autoFocus
/>

Recipes #

Controlled with verification flow

const [code, setCode] = useState("");
const [status, setStatus] = useState<"idle" | "verifying" | "error">("idle");

<OTPInputStyled
  length={6}
  value={code}
  onChange={(v) => { setCode(v); setStatus("idle"); }}
  onComplete={async (final) => {
    setStatus("verifying");
    try { await verify(final); } catch { setStatus("error"); }
  }}
  error={status === "error" ? "Invalid code" : undefined}
  disabled={status === "verifying"}
/>

Alphanumeric pattern (e.g. license keys)

<OTPInputStyled
  length={8}
  pattern="alphanumeric"
  groupSize={4}
  uppercase
  label="License key"
/>

Masked PIN entry

<OTPInputStyled
  length={4}
  mask
  pattern="numeric"
  label="PIN"
  hint="Hidden for privacy"
/>

Headless #

import { useOTP } from "@mshafiqyajid/react-otp-input";

const { slots, value, isComplete } = useOTP({
  length: 6,
  pattern: "numeric",
  onComplete: (code) => verify(code),
});

return (
  <div className="otp">
    {slots.map((slot) => (
      <input key={slot.index} {...slot.inputProps} className="otp__slot" />
    ))}
  </div>
);

API #

OTPInputStyled

PropTypeDefaultDescription
lengthnumber6Number of slots.
valuestringControlled value.
onChange(value: string) => voidFires on change.
onComplete(value: string) => voidFires when all slots filled.
patternnumeric | alphanumeric | any | RegExp | fnnumericAllowed characters.
variantsolid | outline | underlinesolidVisual style.
sizesm | md | lgmdSize.
toneneutral | primary | success | dangerneutralColor theme.
maskbooleanfalseShow maskChar instead of typed char.
groupSizenumberInsert separator after every N slots.
autoFocusbooleanfalseFocus first slot on mount.
labelReactNodeLabel above.
hintReactNodeHelper text below.
errorReactNodeError text — flips tone to danger.
Edit this page on GitHub