InputOTP
Pre-alphaThe registry and the CLI are not published yet.Roadmap

InputOTP

One-time code entry with separate cells.

Check your texts

Enter the 6-digit code sent to +44 7700 900123.

481

Didn’t get it? Resend in 0:24

Installation

npx axiom add input-otp
pnpm dlx axiom add input-otp
yarn dlx axiom add input-otp
bun x axiom add input-otp

The input logic, the caret blink and the error shake live in use-input-otp.ts. Both animations are skipped when Reduce Motion is on.

Usage

import { InputOTP } from "@/components/ui/input-otp";

<InputOTP length={6} value={code} onChange={setCode} onComplete={verify} />;

Props

PropTypeDefaultDescription
lengthnumber6Number of cells.
valuestringThe code typed so far.
defaultValuestring''Initial code when the component manages its own value.
onChange(value: string) => voidCalled on every keystroke, paste or deletion.
onComplete(value: string) => voidCalled once all cells are filled. Good place to submit.
type'numeric' | 'alphanumeric''numeric'Picks the keyboard and filters what can be typed or pasted.
groupsnumber[]Splits cells into groups with a separator, e.g. [3, 3] for 123–456.
autoFocusbooleantrueOpens the keyboard on the first empty cell when the screen appears.
securebooleanfalseShows dots instead of digits, for a PIN.
errorbooleanfalseRed borders and a shake. Clear the value to let the user retry.
successbooleanfalseGreen cells, once the code is accepted.
disabledbooleanfalseBlocks input, for example while the code is being verified.
accessibilityLabelstring'Verification code'What screen readers announce for the field.
size'sm' | 'md''md'Cell size: 40×48 or 48×56pt.

The field always sets textContentType="oneTimeCode", so iOS suggests the code from Messages above the keyboard, and autoComplete="sms-otp" on Android. It also accepts autoCapitalize, returnKeyType and onSubmitEditing.

Cell colors come from inputOtp.default.{default,active,invalid,success,disabled} in the component tokens.

Under the cells sits one hidden TextInput: it receives the system keyboard, paste and SMS autofill, and the cells only draw its value. This is why deleting and pasting behave like in any text field.

Use cases

Verifying a phone number

Submit in onComplete, then show the result on the cells: green on success, red on failure.

Enter code

4

8

1

9

0

2

Number verified
<InputOTP
	length={6}
	value={code}
	onChange={setCode}
	disabled={status === "verifying"}
	error={status === "error"}
	success={status === "success"}
	onComplete={async (value) =>
		setStatus((await verify(value)) ? "success" : "error")
	}
/>

Wrong code

error marks every cell. Clear the value so the next digit starts a new attempt.

Enter code

4

8

1

9

0

7

That code doesn’t match. 2 attempts left.

Send a new code

<InputOTP
	value={code}
	onChange={(v) => {
		setError(false);
		setCode(v);
	}}
	error={error}
/>

Grouped license key

type="alphanumeric" with groups for codes people copy from somewhere else. Pasting fills every cell.

Redeem

Enter the code printed on your gift card.

K7QM2

Paste from clipboard

<InputOTP
	length={6}
	groups={[3, 3]}
	type="alphanumeric"
	autoCapitalize="characters"
	value={code}
	onChange={setCode}
/>