Input OTP
One-time password and verification code input with accessible digit slots
InputOTP is a multi-slot input for one-time passwords, verification codes, PINs, and short access codes. Each slot uses the same surface, border, focus, radius, typography, and state tokens as Input, while the component adds code-specific keyboard and paste behavior.
Basic usage
import { InputOTP } from '@mosmox/ui';
<InputOTP label="Verification code" />The default length is six characters. Typing advances to the next slot, Backspace moves back when the current slot is empty, arrow keys move between slots, and pasting a code fills consecutive slots from the focused position.
Each slot is a native text input with maxLength={1}. The component renders one hidden form input for the complete value when name is provided; the individual slots are not submitted as separate fields.
Controlled and uncontrolled values
Use defaultValue for an internally managed code:
<InputOTP label="Email code" defaultValue="184209" />Use value and onChange when the parent owns the code:
const [code, setCode] = useState('');
<InputOTP
label="Authenticator code"
value={code}
onChange={setCode}
onComplete={(completedCode) => verifyCode(completedCode)}
/>onComplete fires when the value reaches the configured length. It is useful for starting verification, but the component does not submit forms or make network requests by itself.
When the code is shorter than length, missing slots are represented as empty strings. A controlled value is rendered as provided, while an uncontrolled field starts from defaultValue and updates internally after typing, deletion, or paste.
Length, separators, and sizes
Use length for codes from one to twelve characters. Pass separator to visually divide the code into two groups. The separator is inserted at the midpoint:
<InputOTP length={4} label="Short PIN" />
<InputOTP length={6} separator="—" label="Grouped code" />
<InputOTP length={8} separator="·" size="sm" label="Recovery code" />size supports default, sm, and xs, matching the other input components. variant supports outline and filled.
The separator is decorative and is placed after Math.ceil(length / 2) slots. It does not participate in keyboard navigation or form submission.
Numeric and alphanumeric codes
The default inputMode is numeric, which requests a numeric keyboard on touch devices. Use inputMode="text" and pattern for alphanumeric codes. validateChar can provide custom per-character validation:
<InputOTP length={8} inputMode="text" pattern="[A-Za-z0-9]" label="Alphanumeric code" />
<InputOTP
length={6}
inputMode="text"
validateChar={(character) => !/[018OIL]/.test(character)}
label="Human-friendly code"
/>Invalid characters are ignored during typing and paste. The component preserves the order of accepted characters.
pattern is evaluated against each individual character, not the complete code. validateChar runs as an additional validator. In numeric mode, only decimal digits are accepted before the other validators are applied. During paste, accepted characters are inserted from the focused slot and truncated at the configured length.
Masking and autofill
Pass mask to hide entered characters while retaining native input behavior. autoFocus focuses the first slot when the component mounts, and selectOnFocus controls whether an existing character is selected when its slot receives focus:
<InputOTP length={6} mask="•" autoFocus label="Security code" />
<InputOTP length={6} selectOnFocus={false} label="Editable code" />mask changes each filled slot to a password input and displays the supplied mask string. The empty slots still display their placeholder, if one is provided.
Keyboard and paste behavior
- Typing a valid character replaces the focused slot and moves focus to the next slot.
Backspaceclears the focused slot; when it is empty, it clears and focuses the previous slot.ArrowLeftandArrowRightmove between slots without changing the value.- Pasting distributes valid characters across consecutive slots starting at the focused slot.
onPastereceives only the characters accepted byinputMode,pattern, andvalidateChar.- Disabled and read-only fields ignore editing and paste changes.
States and form integration
disabled, readOnly, error, description, placeholder, and name are supported. When name is present, the component adds a hidden form input containing the complete code:
<InputOTP
name="verificationCode"
label="Verification code"
description="We sent a code to your email address."
error={codeError}
/>The visible slots expose individual labels such as “Character 1 of 6” to assistive technology and the complete control exposes a group label. The component does not expose the code through an unlabelled text field.
aria-label overrides the generated group name. aria-describedby can reference external help text and is combined with the generated description and error IDs. An error sets aria-invalid on every slot. id becomes the base for the group and slot IDs; a stable ID is generated when it is omitted. fullWidth makes the slot group use the available width, and className is applied to the outer field wrapper.
Theme tokens
Input OTP consumes the shared theme tokens for surface, filled surface, control border, text, placeholder, muted text, primary focus, danger text, radius, typography, icon spacing, and font weights. Customize those values globally through ConfigProvider:
<ConfigProvider
theme={{
colors: {
controlBorder: '#C7D2FE',
controlFilled: '#F8FAFC',
focus: '#4F46E5',
placeholder: '#A5B4FC',
},
radius: '8px',
}}
>
<InputOTP label="Themed verification code" />
</ConfigProvider>Component-only layout rules live in InputOTP.css; shared colors, typography, radius, and spacing remain controlled by the framework token layer.
API Reference
| Property | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of code slots, clamped to 1–12. |
value | string | — | Controlled code value. |
defaultValue | string | '' | Initial uncontrolled code value. |
id | string | generated | Base ID used for the group and slot inputs. |
className | string | — | Class applied to the outer field wrapper. |
onChange | (value: string) => void | — | Called whenever the code changes. |
onComplete | (value: string) => void | — | Called when all slots are filled. |
onPaste | (value: string) => void | — | Called with accepted characters from a paste action. |
mask | string | — | Masks filled slots with the provided character. |
pattern | string | — | Regular expression pattern applied to each character. |
validateChar | (character: string) => boolean | — | Custom character validator. |
separator | ReactNode | — | Visual separator inserted at the midpoint. |
placeholder | string | — | Placeholder applied to every empty slot. |
autoFocus | boolean | false | Focuses the first slot on mount. |
selectOnFocus | boolean | true | Selects the existing character when a slot receives focus. |
size | InputSize | default | default, sm, or xs. |
variant | InputVariant | outline | outline or filled. |
inputMode | InputMode | numeric | Requested virtual keyboard mode. |
label | ReactNode | — | Accessible field label. |
description | string | — | Supporting text. |
error | string | — | Validation message and invalid styling. |
aria-label | string | generated | Accessible name for the slot group. |
aria-describedby | string | — | Additional accessible description ID(s). |
fullWidth | boolean | false | Makes the slot group fill its parent width. |
disabled | boolean | false | Disables every slot. |
readOnly | boolean | false | Prevents edits while keeping the value visible. |
name | string | — | Form field name for the hidden complete-value input. |