Input OTP
A one-time-code input; a row of single-character slots for verification and PIN codes, compose inside a Field.
Usage
A row of single-character slots for entering a one-time code. Compose it inside a Field for the label, description, and error wiring.
import { InputOtp } from '@epilot/spark-ui/input-otp'function VerifyStep() {
return (
<Field>
<FieldLabel>Verification code</FieldLabel>
<InputOtp onComplete={(code) => submit(code)} />
</Field>
)
}Typing advances to the next slot, backspace moves back, and pasting a full code fills every slot at once. Use it for short verification or PIN codes; for a plain value use Input, and for a value with a leading or trailing addon use Input Group.
Examples
Centered verification screen
The usual OTP layout centers everything. text-center on the Field centers the
label and description; justify-center on the control centers the slot row.
No dedicated prop needed.
Length
length sets the slot count (default 6). A 4-slot field suits a shorter PIN.
Invalid
Set data-invalid on the Field. Every slot switches to the error palette; pair it
with a FieldError for the message.
Disabled
Set data-disabled on the Field and disabled on the control. All slots gray out
and stop accepting input; the label keeps full contrast.
API Reference
InputOtp
Renders Base UI OTP Field. The
style and color follow the enclosing Field, else the data-input-style (soft)
and data-input-color (brand) knobs, so the slots match the rest of the input
family. autoComplete="one-time-code" and a numeric keyboard are on by default.
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of code slots. |
value | string | — | Controlled value. Provide onValueChange to update it. |
defaultValue | string | — | Initial value when uncontrolled. |
onValueChange | (value: string, details) => void | — | Fires on every edit (typing, paste, clear); details.reason names what triggered it. |
onComplete | (value: string, details) => void | — | Fires when the last empty slot is filled. Maps to Base UI onValueComplete. |
slotAriaLabel | (index: number) => string | `Digit ${index + 1}` | Accessible name per slot (override to localize). The first slot is named by the Field label. |
disabled | boolean | false | Grays out every slot and ignores input. |
id | string | Field | Id of the first slot; the rest derive from it. Defaults to the enclosing Field's id. |
className | string | — | Merges onto the slot row (the Base UI OTPField.Root). |
Field state attributes
Set on Field; the composition reacts as a whole.
| Attribute | Pair with | Effect |
|---|---|---|
data-invalid | FieldError for message | Every slot turns error red |
data-disabled | disabled on InputOtp | Slots gray out; the label keeps full contrast |
Accessibility
- The slots form one
role="group"labelled by theFieldLabel; the label names the group and its first slot, and the remaining slots get positional names (Digit 2,Digit 3, …). Always provide aFieldLabelso the first slot is named. - A
FieldDescriptionis wired to the first slot viaaria-describedby. - Arrow keys move between slots,
Home/Endjump to the first/last, and pasting a full code distributes it across the slots.