Inputs
OTP Input
The one-time code field.
import { OtpInput } from 'omaris' Examples
Basic
Six digits. Try pasting 483920 — the row fills in one go.
Waiting for six digits
<script lang="ts"> import { OtpInput } from 'omaris'; let code = $state('');</script><div class="flex flex-col items-center gap-3"> <OtpInput bind:value={code} /> <p class="text-sm text-muted-foreground">{code || 'Waiting for six digits'}</p></div> Shapes
Three fills, and a break where the code is read with one.
<script lang="ts"> import { OtpInput } from 'omaris';</script><div class="flex flex-col items-center gap-5"> <OtpInput variant="outline" value="483920" groups={[3, 3]} /> <OtpInput variant="filled" value="483920" /> <OtpInput variant="underline" value="483920" /></div> States
A wrong code shakes once and turns destructive; a PIN masks what it holds.
<script lang="ts"> import { OtpInput } from 'omaris'; let pin = $state('1234');</script><div class="flex flex-col items-center gap-5"> <OtpInput value="000000" invalid /> <OtpInput length={4} bind:value={pin} mask label="PIN" /> <OtpInput length={8} value="A7F2QK" pattern="alphanumeric" uppercase groups={[4, 4]} size="sm" /></div> Overridden
Round boxes, a wider gap and a monospace face — the sizing still holds.
<script lang="ts"> import { OtpInput } from 'omaris';</script><OtpInput value="4839" length={4} size="lg" class="gap-3" classes={{ slot: 'rounded-full', char: 'font-mono' }}/> When to use it
Use it for
- The six digits from an SMS, an email or an authenticator app, on the screen whose whole job is that code.
autofocusbelongs here and almost nowhere else. - A short recovery or invite code. Use
pattern="alphanumeric"withuppercase, andgroupswhere the code is printed with a break. - A numeric PIN, with
maskso it cannot be read over a shoulder. - Any field where the length is fixed and known, and seeing how much is left to type helps.
Not for
- A code of unknown or variable length → Text Field with
format="numeric". Boxes promise a length. - A password → a
Text Fieldwithtype="password", which gets the reveal toggle and the password manager. - Anything longer than about eight characters. The row stops fitting a phone.
- A quantity → Number Input.
Do
- Handle
oncompleteand submit from it. A filled code should not need a button press. - Set
patternto what the code really is. Everything else is dropped on the way in, typed or pasted, so a code copied with spaces or dashes still lands. - Match
groupsto how the code is written elsewhere:[3, 3]for483-920. It has to add up tolength. - Show the failure on the field.
invalidturns it destructive and shakes it once. Say what to do next in text beneath it. - Leave
autocomplete="one-time-code"alone. It is what makes iOS offer the code above the keyboard.
Don't
- Build the row out of one input per box. Paste, autofill, select-all and backspace across a boundary all break. This component is one input under the boxes for that reason.
- Clear the field on a wrong code. Tapping a box puts the caret in it and the next keystroke replaces that character, so one wrong digit costs one key.
- Disable paste. Every code arrives by being copied from somewhere.
- Put two on one screen. Only one code is entered at a time.
- Wrap it in something that flips direction. The row pins itself to
ltrwhatever the page does; a code reads left to right in every language.
Quick reference
variant outline(default)filledunderline
size smmd(default)lg
API
OtpInput
The one-time code field.
One real <input> lies invisibly across the whole row and the boxes are drawn under it. That is the part worth knowing: a code field built from one box per digit has to reimplement paste, autofill, select-all, backspace across a boundary and every mobile keyboard's idea of what a character is — and gets at least one of them wrong. Here the browser does all of it, so pasting 123 456 from a message fills the row, iOS's one-time-code suggestion lands, ⌘A selects the code and ⌫ clears it.
import { OtpInput } from 'omaris' <OtpInput bind:value={code} oncomplete={verify} /><OtpInput length={8} pattern="alphanumeric" groups={[4, 4]} /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
slot- One box.
data-activewhile the caret is in it. char- The character in the box.
caret- The blinking bar shown in the empty box the caret is in.
separator- The gap drawn between two groups. Inert, for the same reason a box is.
control- The control itself — transparent, and exactly over the row.
Props
value bindableDefaults to ''
string The code so far. Bindable, and never longer than length.
length Defaults to 6
number How many characters the code has.
variant Defaults to 'outline'
OtpInputVariant outline- A box per digit — the default, and the one people expect.
filled- A tinted well, no border until the caret arrives.
underline- A rule under each digit, for a lighter form.
size Defaults to 'md'
OtpInputSize smmdlg
pattern Defaults to 'digits'
OtpPattern What the field accepts. Anything else is dropped, typed or pasted.
uppercase Defaults to false
boolean Upper-case every letter as it lands — codes are usually read aloud.
groups number[] Split the row — [3, 3] for 123-456. Must add up to length.
separator Defaults to '–'
string What sits between two groups.
mask Defaults to false
boolean Show dots instead of the characters.
invalid boolean Marks the row invalid and shakes it once. Inside a Field, defaults to the field's.
disabled boolean Inside a Field, defaults to the field's.
readonly Defaults to false
boolean autofocus Defaults to false
boolean Take focus on mount — right on a screen whose only job is the code.
label string Accessible name for the whole row. "Verification code" when omitted — or, inside a Field, the field's own label.
oncomplete (value: string) => void Fires the moment the last character lands, typed or pasted.
onchange (value: string) => void Fires on every change, complete or not.
slot Snippet<[{ char: string; index: number; active: boolean; filled: boolean }]> Replaces a box's contents. Gets the character and the box's state.
class string classes OtpInputClasses Per-part Tailwind overrides. class still covers the root.
ref bindableDefaults to null
HTMLInputElement | null