Skip to content
omaris

Inputs

OTP Input

The one-time code field.

import { OtpInput } from 'omaris'
Learn

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. autofocus belongs here and almost nowhere else.
  • A short recovery or invite code. Use pattern="alphanumeric" with uppercase, and groups where the code is printed with a break.
  • A numeric PIN, with mask so 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 Field with type="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 oncomplete and submit from it. A filled code should not need a button press.
  • Set pattern to 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 groups to how the code is written elsewhere: [3, 3] for 483-920. It has to add up to length.
  • Show the failure on the field. invalid turns 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 ltr whatever the page does; a code reads left to right in every language.

Quick reference

variant
  • outline (default)
  • filled
  • underline
size
  • sm
  • md (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-active while 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 bindable

Defaults 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
sm
md
lg
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 bindable

Defaults to null

HTMLInputElement | null