InputOTP
One-time code entry with separate cells.
Installation
npx axiom add input-otppnpm dlx axiom add input-otpyarn dlx axiom add input-otpbun x axiom add input-otpThe 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
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of cells. |
value | string | — | The code typed so far. |
defaultValue | string | '' | Initial code when the component manages its own value. |
onChange | (value: string) => void | — | Called on every keystroke, paste or deletion. |
onComplete | (value: string) => void | — | Called once all cells are filled. Good place to submit. |
type | 'numeric' | 'alphanumeric' | 'numeric' | Picks the keyboard and filters what can be typed or pasted. |
groups | number[] | — | Splits cells into groups with a separator, e.g. [3, 3] for 123–456. |
autoFocus | boolean | true | Opens the keyboard on the first empty cell when the screen appears. |
secure | boolean | false | Shows dots instead of digits, for a PIN. |
error | boolean | false | Red borders and a shake. Clear the value to let the user retry. |
haptic | HapticKind | false | 'error' | Haptic played when error turns on. false turns it off. |
success | boolean | false | Green cells, once the code is accepted. |
disabled | boolean | false | Blocks input, for example while the code is being verified. |
accessibilityLabel | string | '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.
Haptics
When error turns on, the field plays an error haptic along with the shake, so a wrong code is felt even without looking. Unlike the shake, it still plays with Reduce Motion on. Pass haptic={false} to turn it off. Nothing plays until your app connects a haptics library: see Haptics.
Use cases
Verifying a phone number
Submit in onComplete, then show the result on the cells: green on success, red on failure.
<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.
<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.
<InputOTP
length={6}
groups={[3, 3]}
type="alphanumeric"
autoCapitalize="characters"
value={code}
onChange={setCode}
/>Related
- OTP verification pattern
- Passcode, which uses an in-app keypad instead of the system keyboard