Skip to content

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.

tsx
import { OtpField } from 'neba';

<OtpField label="Verification code" length={6} groupSize={3} onComplete={(code) => verify(code)} />;

Props

PropTypeDefaultDescription
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
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 means no shadow at all
lengthnumber6How 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
maskbooleanfalseHides the characters, the way a password field does
groupSizenumberSplits the row every groupSize slots. 3 on a six digit code gives the familiar two blocks of three
separatorReactNode'–'What is drawn between two groups
valuestringThe code. Use with onValueChange for a controlled field
defaultValuestringWhat it starts as
onValueChange(value: string) => voidFires when the value changes
onComplete(value: string) => voidFires once every slot is filled: the moment to verify the code
onValueInvalid(value: string) => voidFires when typed or pasted text held characters the charset rejects
autoSubmitbooleanfalseSubmits the owning form as soon as the code is complete
labelReactNodeThe label, wired to the control by Base UI's Field
descriptionReactNodeHelper text
errorReactNodeError message. Its presence turns the control invalid and re-points the colour family at danger
invalidboolean!!errorForces the invalid state without a message, for when a form library owns validity
namestringIdentifies the field when a form is submitted. It lands on the clipped input carrying the whole value
requiredbooleanfalseThe form must have a complete code before it submits
readOnlybooleanfalseShown but not changeable. Keeps its colour and edge, drains the saturation
disabledbooleanfalseUnavailable. Drops the colour family for neutral grey
autoFocusbooleanfalsePuts 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.

tsx
<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, description and error are wired to the slots, so all three are announced with the field.

Released under the MIT License