Skip to content
WANCRAFT

F.24Forms & Inputs

Animated OTP Input

One-time-code entry with paste, SMS autofill, keyboard navigation and an error shake.

ReactMotionTailwindintermediate

Overview

Animated OTP Input renders a labelled group of single-character inputs. Typing advances, Backspace clears and then steps back, arrows/Home/End move between slots, and paste or SMS autofill (autoComplete="one-time-code" on the first slot) distributes characters across the row. Characters spring into place, a caret blinks in the focused empty slot, and an invalid state shakes the row. It works controlled or uncontrolled, supports numeric or alphanumeric codes and masking, and fires onComplete when every slot is filled.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/otp-input.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/otp-input.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/otp-input.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
1 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { OtpInput } from "@/components/ui/otp-input";

export function OtpInputExample() {
  return (
    <OtpInput onComplete={(code) => console.log(code)} />
  );
}

03Props

PropTypeDefaultDescription
lengthnumber6Number of slots (1–12).
value / defaultValuestring—Controlled value, or initial value when uncontrolled.
onChange(value: string) => void—Called with the current code on every change.
onComplete(value: string) => void—Called when every slot is filled.
mode"numeric" | "alphanumeric""numeric"Allowed characters; alphanumeric input is upper-cased.
errorReactNode—Truthy marks inputs invalid and shakes the row; a string/node is shown and announced. Clear it on change to re-arm the shake.
labelstring"Verification code"Group label (aria-labelledby).
hideLabelbooleanfalseVisually hides the label while keeping it accessible.
maskbooleanfalseShows dots; inputs become type="password".
separatorbooleantrueDivider in the middle of even-length codes (4+).
size"md" | "lg""md"Slot size.
accentstring"#FF7448"Focus border, ring and caret colour.
namestring—Submits the code with native forms via a hidden input.
disabled / autoFocusbooleanfalseDisables all slots / focuses the first slot on mount.

04Features

  • 01Paste and SMS autofill distribute characters across slots
  • 02Backspace clears, then steps back; Delete clears in place; arrows, Home and End navigate
  • 03Clicking past the first empty slot focuses the first empty slot
  • 04Spring-in characters, blinking caret and an error shake
  • 05Controlled and uncontrolled, numeric or alphanumeric, optional masking
motion · springmotion · tweeninput · keyboardinput · focusinput · click

05Accessibility

  • role="group" labelled by a visible (or visually hidden) label.
  • Each input is named "Digit 1 of 6" (or "Character…") and is a real, focusable <input>.
  • First input uses autoComplete="one-time-code"; numeric mode uses inputMode="numeric".
  • Errors set aria-invalid and aria-describedby, with the message in a polite live region.
  • prefers-reduced-motion disables the shake, character spring and caret blink.