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-fieldAPI
| Prop | Type | Default | Description |
|---|---|---|---|
| length | number | 6 | Number of slots. |
| invalid | boolean | — | Paints destructive slots, shakes the group, and fires the error haptic when flipped on. |
| value / defaultValue | string | — | Controlled / uncontrolled code. |
| onValueChange | (value, eventDetails) => void | — | Fires as digits land or are deleted. |
| onValueComplete | (value, eventDetails) => void | — | Fires 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
invalidshakes 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.