We sent a code to hello@beste.co. Try 246810.
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.
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"} />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.
| Type | Fills the slot under the caret and moves to the next one. |
|---|---|
| Backspace | Clears the slot before the caret and steps back. |
| Left / Right | Move between slots; typing then overwrites the slot you land on. |
| Paste or SMS autofill | Fills every slot at once. |
| Click a slot | Puts the caret there, up to the first empty slot. |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | — | Number of slots. @defaultValue 6 |
value | string | — | Controlled code. |
defaultValue | string | — | 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" |
separator | number | number[] | — | A small dash after these slot counts, e.g. `3` for 123-456. |
mask | boolean | — | Show dots instead of the characters. |
label | string | — | |
description | string | — | Help text under the slots, read with them. |
errorMessage | string | — | Shown and announced when the code is wrong. @defaultValue "That code didn't work. Try again." |
successMessage | string | — | Shown and announced when the code is right. @defaultValue "Verified" |
name | string | — | |
autoFocus | boolean | — | |
disabled | boolean | — | |
tone | "muted" | "outline" | "ghost" | "outline" | |
size | "sm" | "default" | "lg" | "default" | |
className | string | — |