OTP Input
ericts-ui/otp-inputFreeComponent
A fixed-slot one-time passcode input with paste, autofill, error shake, and animated success feedback.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/otp-input.jsonSource
1"use client";23import * as React from "react";4import {5 AnimatePresence,6 animate,7 motion,8 useReducedMotion,9} from "motion/react";1011import { CheckAnimation } from "@/components/ui/check-animation";12import { cn } from "@/lib/utils";1314export type OTPStatus = "idle" | "error" | "success";1516export interface OTPInputProps17 extends Omit<18 React.ComponentPropsWithoutRef<"div">,19 "defaultValue" | "onChange"20 > {21 /** Number of slots. Default 6. */22 length?: number;23 value?: string;24 defaultValue?: string;25 onChange?: (value: string) => void;26 /** Fires once every slot is filled. */27 onComplete?: (value: string) => void;28 /** Optional label rendered above the slots. */29 label?: string;30 /** Helper text shown below the slots while idle. */31 hint?: string;32 /** Message shown below the slots when status is "success". */33 successMessage?: string;34 /** Message shown below the slots when status is "error". */35 errorMessage?: string;36 /** External validation feedback. "error" shakes, "success" draws a check. */37 status?: OTPStatus;38 /** Render dots instead of the typed digits. */39 mask?: boolean;40 disabled?: boolean;41 autoFocus?: boolean;42 /** Accessible label for the underlying input. */43 "aria-label"?: string;44 /** Accessible label for the success indicator. */45 successIndicatorLabel?: string;46}4748const EASE_OUT = [0.16, 1, 0.3, 1] as const;4950export function OTPInput({51 length = 6,52 value: controlledValue,53 defaultValue = "",54 onChange,55 onComplete,56 label,57 hint,58 successMessage,59 errorMessage,60 status = "idle",61 mask = false,62 disabled = false,63 autoFocus = false,64 "aria-label": ariaLabel = "One-time passcode",65 successIndicatorLabel = "Code verified",66 className,67 ...props68}: OTPInputProps) {69 const slotCount = normalizeLength(length);70 const uid = React.useId();71 const shouldReduceMotion = useReducedMotion();72 const inputRef = React.useRef<HTMLInputElement>(null);73 const slotsRef = React.useRef<HTMLDivElement>(null);74 const controlled = controlledValue !== undefined;7576 // Source of truth is a fixed-length array, so a cleared middle slot stays an77 // in-place hole instead of collapsing later digits to the left.78 const [slots, setSlots] = React.useState<string[]>(() =>79 toSlots(controlled ? controlledValue : defaultValue, slotCount),80 );81 const [focused, setFocused] = React.useState(false);82 const [active, setActive] = React.useState(0);8384 const stateSlots = React.useMemo(85// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from ericts-ui
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Animationcheck-animation | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-modal | ericts-ui | Components | Free | 1 dep | |
| Expandable Tabsexpandable-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Toolbarexpandable-toolbar | ericts-ui | Components | Free | 1 dep | |
| Expanding Panelexpanding-panel | ericts-ui | Components | Free | 2 deps |
