Enter the 6 digit code we sent to your phone.
import { Field, FieldDescription, FieldLabel } from "@hel/ui/field"
import { OTPField, OTPFieldInput } from "@hel/ui/otp-field"
When to use it#
Use an OTP field to enter a short code that was sent to the person: a text message code, an email code, a recovery code or a PIN. Each character gets its own box, so the length is obvious and the cursor moves on by itself.
Typing, deleting and pasting all work the way people expect. A pasted code fills every box at once.
Import#
import { OTPField, OTPFieldInput, OTPFieldSeparator } from "@hel/ui/otp-field"Anatomy#
OTPField is the row and takes the length. Put one OTPFieldInput inside it for each character, so a 6 character code has six inputs. Render them with a loop, as the example above does.
<OTPField length={6}>
{Array.from({ length: 6 }, (_, index) => (
<OTPFieldInput key={index} aria-label={`Digit ${index + 1} of 6`} />
))}
</OTPField>Naming the inputs#
A screen reader needs a name for the whole code and for each box. Inside a Field, the label names the first box. Give the others an aria-label like "Digit 2 of 6", and leave the first one without, as the example does. Outside a field, give the field an aria-label and every box one too.
Examples#
Reacting to the finished code#
onValueComplete is called once, with the whole code, when the last box is filled, which is the moment to check it. onValueChange fires on every change. Set autoSubmit to submit the surrounding form by itself when the code is complete.
Waiting for the code
import * as React from "react"
import { OTPField, OTPFieldInput } from "@hel/ui/otp-field"Grouped boxes#
Long codes are easier to read in chunks. Wrap each chunk in a Flex and put an OTPFieldSeparator between them. The boxes still belong to one field, so typing flows from one chunk to the next.
import { Flex } from "@hel/ui/flex"
import { OTPField, OTPFieldInput, OTPFieldSeparator } from "@hel/ui/otp-field"
Letters, numbers and hidden characters#
The field accepts digits only by default. Set validationType="alphanumeric" for codes that mix letters and numbers. mask hides what is typed behind dots, which suits a PIN on a shared screen.
Recovery code, letters and numbers
Access PIN, hidden as you type
import { OTPField, OTPFieldInput } from "@hel/ui/otp-field"
import { Stack } from "@hel/ui/stack"
import { Text } from "@hel/ui/text"Sizes#
size on the field sizes every box: xs, sm, md, lg or xl, which is the default. They match the heights of other controls, so an OTP field lines up with buttons and inputs next to it. A single box can override it with its own size.
States#
disabled and readOnly apply to every box, and required marks the code as needed. Inside an invalid Field, the boxes get the error ring.
Props#
OTPField#
| Prop | Type | Default |
|---|---|---|
length | number | required |
value / defaultValue | string | none |
onValueChange | (value: string) => void | none |
onValueComplete | (value: string) => void | none |
validationType | "numeric" | "alphanumeric" | "numeric" |
mask | boolean | false |
autoSubmit | boolean | false |
size | "xs" | "sm" | "md" | "lg" | "xl" | "xl" |
disabled / readOnly / required | boolean | false |
name | string | none |
It also takes every prop of the Base UI OTP field.
OTPFieldInput#
One box. It takes size to override the field's, and every prop of an input, such as aria-label.
OTPFieldSeparator#
A divider between chunks. It takes the props of a Separator.