Components · OtpInput
OtpInput
Six visible slots over one real input. Type, paste the whole code, or navigate with the arrow keys — screen readers hear a single labelled field, not six unlabelled boxes.
import { Field, OtpInput } from "@luc/ui";Examples
Basic
`autocomplete="one-time-code"` is set for you, and the pattern decides which characters the field accepts.
Six digits, sent to research@acme.dev.
value: —
<Field
label="Verification code"
description="Six digits, sent to research@acme.dev."
success={complete ? "Six digits entered — this is where the check runs." : undefined}
>
<OtpInput
length={6}
groups={[3, 3]}
value={code}
onValueChange={setCode}
onComplete={() => setComplete(true)}
/>
</Field>Characters, grouping and sizes
<OtpInput aria-label="Alphanumeric code" length={6} mode="alphanumeric" />
<OtpInput aria-label="Grouped code" length={8} groups={[4, 4]} defaultValue="1234" />
<OtpInput aria-label="Small code" length={4} size="sm" />
<OtpInput aria-label="Large code" length={4} size="lg" />States
Empty, rejected and locked: the invalid state is a message on the field, and a disabled code keeps its digits readable.
Empty
That code has expired
Invalid
field-level error
Disabled
<OtpInput length={4} aria-label="Code" />
<Field label="Code" error="That code has expired">
<OtpInput length={4} defaultValue="1234" />
</Field>
<OtpInput length={4} defaultValue="1234" disabled aria-label="Disabled code" />Usage
import { OtpInput } from "@luc/ui";<OtpInput length={6} groups={[3, 3]} value={code} onValueChange={setCode} aria-label="Verification code" />`length` fixes the number of slots; `onComplete` fires once the last one is filled, which is where the check belongs.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
OtpInput | 14 documented props | ({ length, value, defaultValue, onValueChange, onComplete, mode, size, invalid, disabled, groups, className, id, name, ...props }: OtpInputProps) => import("@types/react").JSX.Element |
OtpInput props
Type: OtpInputProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
aria-label? | string | undefined | — | Accessible name. A code field is usually labelled by its `<Field>`. |
className? | string | undefined | — | |
defaultValue? | string | undefined | — | |
disabled? | boolean | undefined | — | |
groups? | number[] | undefined | — | Groups the slots visually, e.g. `3` renders 3+3 for a six-digit code. |
id? | string | undefined | — | |
invalid? | boolean | undefined | — | |
length? | number | undefined | — | Number of characters. |
mode? | OtpMode | undefined | — | |
name? | string | undefined | — | |
onComplete? | ((value: string) => void) | undefined | — | Fired when every slot is filled. |
onValueChange? | ((value: string) => void) | undefined | — | |
size? | ControlSize | undefined | — | |
value? | string | undefined | — |
Guidelines
Do
- Give it `length` and, when the provider breaks the code up, `groups` — six digits read better as three and three.
- Run the check from `onComplete` rather than watching the value, so it fires once instead of on every keystroke.
- Set `mode` only when the code really is alphanumeric; the default keeps a numeric keypad on a phone.
Don't
- Ask for more characters than the provider issues: extra empty slots invite a typo and a failed code.
- Build the slots out of separate inputs — the group is announced as one field, and separate boxes lose that.
- Put the resend control inside the code field; resending belongs to the page that owns the request.
Accessibility
The slots are one labelled field: assistive technology hears a single input of `length` characters carrying `autocomplete="one-time-code"`, not a row of unlabelled boxes. Typing advances, Backspace retreats, a pasted code fills every slot at once, and `invalid` reaches the input from a `Field` that owns the message.
Related