PinInput
A pin input collects a one-time password or PIN. Each cell is a native
<input> labelled "Digit n of N"; the root is a role="group". The value
behaves like one text field: Backspace in a filled cell deletes it and
shifts later cells left, in an empty cell it deletes the previous one and
moves back.
import PinInput from '@zuilib/components/pin-input'
Basic
onChange fires with the typed prefix on every edit; onComplete fires
once, when the value becomes length long. otp marks the cells
autocomplete="one-time-code" so the OS can offer a received SMS code.
Types, mask and placeholder
numeric (the default) accepts digits and opens the numeric keypad;
alphanumeric accepts letters too. mask obscures the characters with
-webkit-text-security: disc while the cells stay type="text", so
inputmode, autofill and password managers keep behaving.
Sizes
Cells are squares the height of the control of the same size.
In a form item
Inside a FormItem the Label names the group and focuses the first
cell, the item's Description and Message join every cell's
aria-describedby, and invalid / disabled are inherited. name adds a
hidden input so the joined value submits with a native form.
Props
PinInputProps extends React.HTMLAttributes<HTMLDivElement> (minus
onChange, defaultValue, placeholder) with:
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of cells |
value | string | — | Controlled value: the typed characters in order, at most length long |
defaultValue | string | '' | Uncontrolled initial value |
onChange | (value: string) => void | — | Every edit, with the joined (gap-free) value |
onComplete | (value: string) => void | — | Once, when the value becomes complete |
type | 'numeric' | 'alphanumeric' | 'numeric' | Accepted characters; numeric also sets inputmode="numeric" and pattern="[0-9]*" |
mask | boolean | false | Obscures the cells with -webkit-text-security: disc |
placeholder | string | — | One character shown in an empty cell |
invalid | boolean | FormItem invalid | aria-invalid / data-invalid on the cells and the destructive colour |
disabled | boolean | FormItem disabled | |
name | string | — | Renders a hidden input with the joined value for native form submission |
form | string | — | The hidden input form attribute |
otp | boolean | false | autocomplete="one-time-code" on the cells |
autoFocus | boolean | false | Focuses the first cell on mount |
id | string | Field control id | Goes on the first cell (what a Label htmlFor focuses) |
cellLabel | (index: number, length: number) => string | 'Digit n of N' / 'Character n of N' | Accessible name of each cell |
size | 'sm' | 'md' | 'lg' | 'md' | |
className | string | — | Merged last onto the root |
cellClassName | string | — | Merged last onto every cell |
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="pin-input"] | div | Root (role="group"), with data-size, data-type, data-state="default|error", data-invalid, data-disabled, data-complete, data-masked |
[data-slot="pin-input-cell"] | input | One per character, with data-index, data-state, data-filled, data-invalid, data-disabled |
[data-slot="pin-input-hidden"] | input[type=hidden] | Present when name is set |
/* Consumer CSS: tint filled cells */
[data-slot="pin-input-cell"][data-filled] {
background-color: var(--accent);
}
Tokens
| Token | Used for |
|---|---|
--control-height-sm/md/lg | Cell width and height per size |
--input, --background, --foreground | Cell border, surface and text |
--ring, --destructive | Focus ring, error state |
--muted-foreground | Placeholder |
--duration-fast | Colour transition |
Accessibility
- The root is
role="group". Inside aFormItemthe item'sLabelnames it througharia-labelledbyand itshtmlForfocuses the first cell; outside one, passaria-labeloraria-labelledby. - Every cell has its own name (
Digit 3 of 6),aria-describedbyfrom the item'sDescription/Message, andaria-invalidwhen invalid. - Keyboard: a character fills the cell and moves on; Backspace deletes (and moves back from an empty cell); Delete clears the cell; ArrowLeft / ArrowRight, Home / End move between cells. Focusing a cell selects its content, so typing over a filled cell replaces it.
- Paste, IME commits and autofill of any length spread from the focused cell on; characters the
typerejects are dropped. - Disabled cells use the native attribute.
Related
- Input: the single-field text control.
- NumberInput: for numbers rather than codes.
- FormItem: label, description and message wiring.