InputOTP
A one-time code in separate slots, with paste, SMS autofill and a pattern.
Autofocus
autofocus through attrs puts the cursor in the first slot when the page loads, the way a verification screen does.
Code
<InputOTP value={state.autofocus} maxLength={6} attrs={{ autofocus: true }} />Usage
import { InputOTP, REGEXP_ONLY_DIGITS } from "@elements/ui/input-otp";
<InputOTP value={form.code} maxLength={6} groups={[3, 3]} pattern={REGEXP_ONLY_DIGITS} />
<InputOTP value={form.pin} maxLength={4} onComplete={(code) => verify(code)} />Examples
Basic
Six slots in two groups. Type, paste, or let the phone fill the code from an SMS.
Code
<InputOTP value={state.code} maxLength={6} groups={[3, 3]}
onValueChange={() => state.changes++}
onComplete={() => state.completes++} attrs={{ "aria-label": "Code" }} />Digits only
pattern={REGEXP_ONLY_DIGITS} refuses anything else, typed or pasted.
Code
<InputOTP value={state.digits} maxLength={6} pattern={REGEXP_ONLY_DIGITS} />Separator
groups splits the slots; the separator slot replaces the dash.
Code
<InputOTP value={state.dots} maxLength={6} groups={[2, 2, 2]}>
<span slot="separator" class="dot"></span>
</InputOTP>Controlled
value is two-way: set it or clear it from code.
Code
<InputOTP value={state.code} maxLength={6} />
<Button onclick={() => state.code = ""}>Clear</Button>Disabled, read-only and invalid
Disabled takes no input. readonly keeps the code focusable and submitted but refuses typing and paste. invalid draws the danger border on every slot.
Code
<InputOTP value={state.code} disabled />
<InputOTP value={state.code} readonly />
<InputOTP value={state.code} invalid />Four digit PIN
maxLength={4} with a digits pattern and placeholder characters. A PIN is not a password manager's business, so pushPasswordManagerStrategy=none leaves the input at the slots' width. textAlign sets where the hidden input's text sits, which is where a long press lands; inputClass adds a class to the input.
Code
<InputOTP value={state.pin} maxLength={4} pattern={REGEXP_ONLY_DIGITS} placeholder="0000"
pushPasswordManagerStrategy="none" textAlign="center" inputClass="pin-input" />Letters and digits
REGEXP_ONLY_DIGITS_AND_CHARS with a text keyboard. pasteTransformer drops dashes and uppercases a pasted code.
Code
<InputOTP value={state.ref} maxLength={6} inputmode="text"
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
pasteTransformer={(t) => t.replace(/-/g, "").toUpperCase()} />In a form
name submits the code; required blocks a partial one.
Code
<form onsubmit={(e) => verify(e)}>
<Field label="Verification code">
<InputOTP value={state.code} maxLength={6} groups={[3, 3]} name="code" required />
</Field>
<Button type="submit" variant="primary">Verify</Button>
</form>Sizes
Slots are squares of the control height.
Code
<InputOTP value={state.code} maxLength={4} size="sm" />
<InputOTP value={state.code} maxLength={4} size="lg" />Right to left
Under rtl the slots run right to left.
Code
<div dir="rtl"><InputOTP value={state.code} maxLength={6} groups={[3, 3]} /></div>