Input OTP
A specialized input component designed for entering one-time passwords (OTPs), featuring accessibility enhancements, automatic focus shifting between inputs, and seamless copy-paste support.
Component Foundation: input-otp
This Input OTP component is built upon the dedicated input-otp library, developed by Guilherme Rodz. This underlying library provides the core logic for handling OTP input behavior, including focus management, pasting, and accessibility.
Installation Procedure
Run the following command:
npx shadcn@latest add input-otpUpdate tailwind.config.js
Add the following animations to your tailwind.config.js file:
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
extend: {
keyframes: {
"caret-blink": {
"0%,70%,100%": { opacity: "1" },
"20%,50%": { opacity: "0" },
},
},
animation: {
"caret-blink": "caret-blink 1.25s ease-out infinite",
},
},
},
};Implementing the Input OTP Component
InputOTP, // The main container component managing the OTP input state and logic.
InputOTPGroup, // A container used to group multiple InputOTPSlot components.
InputOTPSeparator, // Optional component to render a visual separator between groups.
InputOTPSlot, // Represents a single character input slot within the OTP field.
} from "@/components/ui/input-otp"; // Adjust import path as necessary.// Basic structure for a 6-digit OTP input with a separator
<InputOTP maxLength={6}>
{/* First group of input slots */}
<InputOTPGroup>
<InputOTPSlot index={0} /> {/* Slot for the first digit */}
<InputOTPSlot index={1} /> {/* Slot for the second digit */}
<InputOTPSlot index={2} /> {/* Slot for the third digit */}
</InputOTPGroup>
{/* Optional separator */}
<InputOTPSeparator />
{/* Second group of input slots */}
<InputOTPGroup>
<InputOTPSlot index={3} /> {/* Slot for the fourth digit */}
<InputOTPSlot index={4} /> {/* Slot for the fifth digit */}
<InputOTPSlot index={5} /> {/* Slot for the sixth digit */}
</InputOTPGroup>
</InputOTP>The component uses a compositional approach. The main <InputOTP> component defines the maximum length. Inside, <InputOTPGroup> wraps sets of <InputOTPSlot> components. Each <InputOTPSlot> requires a unique index prop corresponding to its position in the sequence (0-based). An optional <InputOTPSeparator> can be placed between groups for visual distinction.
Input OTP Variations and Examples
Custom Input Pattern (Regex)
Control the allowed characters in the OTP input by providing a regular expression to the pattern prop on the <InputOTP> component. The input-otp library exports common patterns like REGEXP_ONLY_DIGITS_AND_CHARS.
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
...
<InputOTP
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
{/* ... */}
</InputOTPGroup>
</InputOTP>Using a Visual Separator
Enhance readability for longer OTPs by inserting the <InputOTPSeparator /> component between <InputOTPGroup> elements. This renders a visual separator (often a dash or space, depending on styling) without being an actual input slot.
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
...
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>Controlled Input Component
Manage the OTP input's value externally using React state by providing the value and onChange props to the <InputOTP> component, similar to standard controlled inputs in React.
Integration within Forms
Integrate the <InputOTP> component into forms built with libraries like React Hook Form. Use the <FormField> component to connect the OTP input to the form state and handle validation.
Component Changelog and Updates
2024-03-19 Composition
We've made some updates and replaced the render props pattern with composition. Here's how to update your code if you prefer the composition pattern.
Note: You are not required to update your code if you are using the
render prop. It is still supported.
Update to the latest version of input-otp.
npm install input-otp@latestUpdate input-otp.tsx
- import { OTPInput, SlotProps } from "input-otp"
+ import { OTPInput, OTPInputContext } from "input-otp"
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
- SlotProps & React.ComponentPropsWithoutRef<"div">
- >(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
+ >(({ index, className, ...props }, ref) => {
+ const inputOTPContext = React.useContext(OTPInputContext)
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]Then replace the render prop in your code.
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>2024-03-19 Disabled
To add a disabled state to the input, update <InputOTP /> as follows:
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName,
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
));
InputOTP.displayName = "InputOTP";