OTP Field

A segmented input for one-time codes, with paste-to-fill and per-slot focus handled by Base UI.

tsx
import { OTPField } from "@/registry/seam/ui/otp-field"

export default function OTPFieldDemo() {
  return <OTPField length={6} defaultValue="123" />
}
bunx --bun @seamui/cli@latest add otp-field

API

PropTypeDefaultDescription
lengthnumber6Number of slots.
invalidbooleanPaints destructive slots, shakes the group, and fires the error haptic when flipped on.
value / defaultValuestringControlled / uncontrolled code.
onValueChange(value, eventDetails) => voidFires as digits land or are deleted.
onValueComplete(value, eventDetails) => voidFires once all slots are filled — the usual submit hook.

Plus all Base UI OTPField.Root props.

Notes

  • Slots are entry wells, so they don't press — but each slot pops as its digit lands, and flipping invalid shakes the group (an opacity flash under reduced motion; the destructive border carries the state either way).
  • The caret is the browser's own — every slot is a real <input>, so nothing fake needs to blink.
  • Uses autoComplete="one-time-code" for OS autofill, moves focus between slots on input, and supports pasting a full code.

Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.