OtpField
A row of one-character slots for a short code typed in from somewhere else. Use it for a PIN, a texted verification code, or an invite key.
import { OtpField } from 'neba';
<OtpField label="Verification code" length={6} groupSize={3} onComplete={(code) => verify(code)} />;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | 'solid' | 'outline' | 'text' | 'outline' | Weight of the surface: filled, hairline, or none |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | A slot's box and the type scale inside it. Its own ladder, because a slot is not a control in a row of controls but a single character standing on its own |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | 'default' | 'compact' | 'default' | The space between slots only |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth. 0 means no shadow at all |
| length | number | 6 | How many characters the code has. Clamped to 2–12: a single box is a TextField, and past twelve the row stops fitting a phone |
| charset | 'numeric' | 'alpha' | 'alphanumeric' | 'any' | 'numeric' | What may be typed. Rejected characters are dropped rather than shown, and reported through onValueInvalid. numeric is the default because that is what a texted code is, and because it puts a number pad in front of a phone |
| mask | boolean | false | Hides the characters, the way a password field does |
| groupSize | number | — | Splits the row every groupSize slots. 3 on a six digit code gives the familiar two blocks of three |
| separator | ReactNode | '–' | What is drawn between two groups |
| value | string | — | The code. Use with onValueChange for a controlled field |
| defaultValue | string | — | What it starts as |
| onValueChange | (value: string) => void | — | Fires when the value changes |
| onComplete | (value: string) => void | — | Fires once every slot is filled: the moment to verify the code |
| onValueInvalid | (value: string) => void | — | Fires when typed or pasted text held characters the charset rejects |
| autoSubmit | boolean | false | Submits the owning form as soon as the code is complete |
| label | ReactNode | — | The label, wired to the control by Base UI's Field |
| description | ReactNode | — | Helper text |
| error | ReactNode | — | Error message. Its presence turns the control invalid and re-points the colour family at danger |
| invalid | boolean | !!error | Forces the invalid state without a message, for when a form library owns validity |
| name | string | — | Identifies the field when a form is submitted. It lands on the clipped input carrying the whole value |
| required | boolean | false | The form must have a complete code before it submits |
| readOnly | boolean | false | Shown but not changeable. Keeps its colour and edge, drains the saturation |
| disabled | boolean | false | Unavailable. Drops the colour family for neutral grey |
| autoFocus | boolean | false | Puts the caret in the first slot on mount |
Every other <div> attribute passes through to the row of slots. color, size and onChange are excluded: the first two are Neba props, and the value is reported by onValueChange. The shared axes are in prop conventions.
Examples
charset
charset decides what may be typed. Characters outside it are dropped rather than shown, and onValueInvalid reports the text they came in on. numeric also puts a number pad in front of a phone; any accepts whatever the keyboard produces.
length and groupSize
length is how many characters the code has, clamped to 2–12. groupSize splits the row every N slots with a separator, which is an en dash unless something else is passed.
mask, error, readOnly and disabled
mask hides the characters. error shows a message and re-points the colour family at danger, and invalid does the same without a message. readOnly keeps the code selectable; disabled stops every slot answering.
size
In a form
name puts the whole value on the form under that name. autoSubmit submits the owning form the moment the code is complete, which is the shape a one-field verification screen wants.
<form action={verify}>
<OtpField name="code" length={6} required autoSubmit />
</form>Accessibility
- Typing moves to the next slot, Backspace steps back over the previous character, and the arrow keys walk the row.
- Pasting a code spreads it across the slots from wherever the caret is, however it was pasted.
- Clicking lands on the first empty slot rather than on the one under the pointer, so a half-typed code cannot be edited into a gap.
- A clipped input carries the whole value for the form and for a phone's autofill;
autocomplete="one-time-code"is on it already. label,descriptionanderrorare wired to the slots, so all three are announced with the field.