Field OTP

We sent a code to hello@beste.co. Try 246810.

About this component

Field OTP

A one-time code field for sign-in and verification: one real input with `autocomplete="one-time-code"` sits under a row of slots, so paste, SMS autofill, password managers and IME all work as they do in any field. Typing fills the slot under the caret and moves on, Backspace steps back, the arrow keys move between slots, and a pasted code fills them all. The active slot shows a blinking caret, each character pops in, and an optional dash splits the row. A `validate` function checks the finished code: a wrong one shakes the row, tints it red and clears it for another try; a right one settles each slot in green and brings in a check. Numeric or alphanumeric, masked or plain, controlled or uncontrolled.

Usage

The import and the props worth knowing about, in one place.

import { FieldOtp } from "@/components/beste/component/field-otp";

<FieldOtp
  label="Verification code"
  description="We sent a code to hello@beste.co."
  separator={3}               // 123-456
  validate={async (code) => {
    const response = await fetch("/api/verify", { method: "POST", body: JSON.stringify({ code }) });
    return response.ok;       // false shakes and clears, true locks it in green
  }}
  onComplete={(code) => console.log("Entered", code)}
/>

// Letters and digits, four slots, shown as dots
<FieldOtp length={4} pattern="alphanumeric" mask tone="muted" size="lg" />

// Status from your own logic
<FieldOtp value={code} onValueChange={setCode} status={verifying ? "checking" : failed ? "error" : "idle"} />

Keyboard and gestures

Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.

TypeFills the slot under the caret and moves to the next one.
BackspaceClears the slot before the caret and steps back.
Left / RightMove between slots; typing then overwrites the slot you land on.
Paste or SMS autofillFills every slot at once.
Click a slotPuts the caret there, up to the first empty slot.

Props

Read from the component's own type, so this cannot drift from what it accepts.

PropTypeDefaultDescription
lengthnumber—Number of slots. @defaultValue 6
valuestring—Controlled code.
defaultValuestring—Initial code when uncontrolled. @defaultValue ""
onValueChange(value: string) => void—
onComplete(code: string) => void—Called once each time every slot is filled.
validate(code: string) => boolean | Promise<boolean>—Checks a complete code. Resolving `false` shakes and clears the slots; `true` locks them in the success state.
status"idle" | "checking" | "error" | "success"—Controlled status; overrides what `validate` would set.
pattern"numeric" | "alphanumeric"—Which characters a slot accepts. `alphanumeric` upper-cases letters. @defaultValue "numeric"
separatornumber | number[]—A small dash after these slot counts, e.g. `3` for 123-456.
maskboolean—Show dots instead of the characters.
labelstring—
descriptionstring—Help text under the slots, read with them.
errorMessagestring—Shown and announced when the code is wrong. @defaultValue "That code didn't work. Try again."
successMessagestring—Shown and announced when the code is right. @defaultValue "Verified"
namestring—
autoFocusboolean—
disabledboolean—
tone"muted" | "outline" | "ghost""outline"
size"sm" | "default" | "lg""default"
classNamestring—

More Field components

View all Field

Use at least 12 characters. A short sentence works well.

Strength
  • At least 12 characters, not met yet
  • A lowercase letter, not met yet
  • An uppercase letter, not met yet
  • A number, not met yet
  • A symbol, not met yet
Control K
Recent searches
  • Nils Frahm
  • Royal Albert Hall
  • Says