@biem/ui

Input OTP

An input that lets users enter a one-time code, one character per slot.

An OTP input takes a short code, such as a one-time password, and shows each character in a slot of its own. A single invisible input covers the slots, so users type, paste and autofill the code as one value. A group joins its slots into one block, and a separator divides two groups.

Usage

input-otp-default.tsx
import {
	InputOTP,
	InputOTPGroup,
	InputOTPSlot,
} from "@biem/ui/components/ui/input-otp";

export function InputOTPDefault() {
	return (
		<InputOTP maxLength={6} aria-label="Pickup code">
			<InputOTPGroup>
				<InputOTPSlot index={0}/>
				<InputOTPSlot index={1}/>
				<InputOTPSlot index={2}/>
				<InputOTPSlot index={3}/>
				<InputOTPSlot index={4}/>
				<InputOTPSlot index={5}/>
			</InputOTPGroup>
		</InputOTP>
	);
}

Examples

With Separator

Codes are easier to read and compare in short parts, the way they often appear on a receipt or a returns label. Between two groups, an InputOTPSeparator marks where one part ends and the next begins. Users still enter the code in one go, and the separator never becomes part of its value.

Digits Only

One-time codes sent by text message usually consist of digits alone. Restricted to digits through pattern, the input rejects every other character, whether users type it or paste it. Touch devices show a numeric keyboard, the default of inputMode.

Letters and Digits

Some codes combine letters and digits, such as the reference of a return or a stock transfer. Once pattern allows both, users can enter either, and inputMode set to "text" gives touch devices a full keyboard, since the numeric keyboard of the default offers no letters.

Controlled

The application can also take charge of the code, for example to fill in a code it already knows or to clear the code after a failed attempt. Through value and onChange it holds the code, so other parts of the interface can read or reset it as well.

4
8
2
9
1
3

Disabled

Entering a code is sometimes not possible yet, for example while the previous attempt is still being checked. Marked as disabled, the input accepts no characters and exposes its state to assistive technology, while users still see where the code will go.

Invalid

A code that has expired or does not match needs to stand out as wrong before users try again. On the input, aria-invalid reports the error to assistive technology, and on every slot it marks the code as rejected, while the input stays ready for a new attempt.

API Reference

InputOTP

PropmaxLength
Typenumber
Default–
DescriptionSets the number of characters in the code. The input accepts no more than that, and one InputOTPSlot stands for each of them. The component requires this prop.
Propvalue
Typestring
Default–
DescriptionSets the code and makes the input controlled. Update it in onChange.
ProponChange
Type(newValue: string) => unknown
Default–
DescriptionRuns when users type, delete or paste characters. It receives the code that follows as a string, not a change event.
ProponComplete
Type(...args: any[]) => unknown
Default–
DescriptionRuns when the code reaches the length of maxLength, whether users type the last character or paste the whole code. It receives the complete code as a string.
Proppattern
Typestring
Default–
DescriptionSets a regular expression that the input tests the code against on every change. The input rejects a change that doesn't match, whether users type or paste it. Anchor the expression with ^ and $, so that it covers every character. Without it, the input accepts any character.
PropinputMode
Type"none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search"
Default"numeric"
DescriptionSets the on-screen keyboard that touch devices show. Set it to "text" when pattern allows letters. It doesn't restrict the characters that the input accepts.
ProppasteTransformer
Type(pasted: string) => string
Default–
DescriptionChanges pasted text before the input takes it over, for example to remove the hyphen from 482-913. The result still has to match pattern.
Propplaceholder
Typestring
Default–
DescriptionSets one placeholder character per slot for the empty input. The input exposes the text to assistive technology as aria-placeholder, and render receives each character as placeholderChar. InputOTPSlot doesn't show placeholder characters.
Propdisabled
Typeboolean
Default–
DescriptionPrevents users from entering a code, dims the input and exposes the disabled state to assistive technology.
ProptextAlign
Type"left" | "center" | "right"
Default"left"
DescriptionSets the text alignment of the invisible input that holds the code. The text of that input is transparent, so the slots look the same with every value.
ProppushPasswordManagerStrategy
Type"increase-width" | "none"
Default"increase-width"
DescriptionSets how the input reacts when a password manager places its badge in the field. With "increase-width", the invisible input grows 40 pixels to the right when that space is free, so that the badge sits beside the last slot and doesn't cover it. With "none", the input keeps its width.
PropcontainerClassName
Typestring
Default–
DescriptionSets classes on the container that lays out the groups and the separators. className goes to the invisible input instead.
PropnoScriptCSSFallback
Typestring | null
Defaultbuilt-in CSS
DescriptionSets the CSS that makes the invisible input visible while JavaScript is off, so that users can still enter the code. The built-in CSS gives the input a border and a background. null leaves the fallback out.
Propnonce
Typestring
Default–
DescriptionSets the nonce of the style element that the input adds to the head of the document. Pass it when a Content Security Policy restricts inline styles.
Proprender
Type(props: RenderProps) => ReactNode
Default–
DescriptionDraws the slots with a function in place of children. The function receives slots, isFocused and isHovering, and each entry of slots holds char, placeholderChar, isActive and hasFakeCaret. InputOTPSlot doesn't work inside the function, because it reads its character from a context that only children receive.

InputOTPGroup

InputOTPGroup holds a row of slots and joins them into one block. It renders a div and has no props of its own.

InputOTPSlot

Propindex
Typenumber
Default–
DescriptionSets the position in the code whose character the slot shows, starting at 0. The part requires this prop.

InputOTPSeparator

InputOTPSeparator places a dash between two groups. It renders a div with the role separator and has no props of its own.