PinInput
2 partsA segmented input for short codes — OTP / one-time passwords, 2FA tokens, or
PINs split across one box per character. The interactive root: it owns the
value as a per-cell string[] (controlled via v-model / update:modelValue
or uncontrolled via defaultValue), sizes the field to length, enforces the
type ('text' | 'number') and mask, and provides context to each
PinInputInput. Emits complete once every cell is filled. Use it for
verification codes where each character gets its own cell with auto-advance,
arrow-key navigation, and clipboard paste spreading across cells.
Native form support: pass name (and optionally required / id) to render
a visually-hidden form control holding the joined value, so the field submits
with its owning <form> and participates in native required validation.
Demo
Anatomy
Import the parts and compose them. Each part forwards attributes to its underlying element.
import {
PinInputInput,
PinInputRoot,
} from '@robonen/primitives/forms/pin-input';
<PinInputRoot>
<PinInputInput />
</PinInputRoot>API Reference
PinInputInput
InputA single cell of the pin input, identified by its zero-based `index`. Renders one masked-or-plain character box that reads/writes its slot of the root's value and handles typing (auto-advancing to the next cell), Backspace/Delete, arrow/Home/End navigation, and paste (spreading text across cells). Render one per character, with `index` from `0` to `length - 1`. Polymorphic via `as` (defaults to a native `<input>`); `as="template"` merges onto a single child. A per-cell `disabled` prop is honored on top of the root-level `disabled` and is skipped by arrow navigation.
| Prop | Type | Description |
|---|---|---|
index | number | — |
disabled? | boolean | Disable this individual cell (merged with the root-level disabled). |
PinInputRoot
RootA segmented input for short codes — OTP / one-time passwords, 2FA tokens, or PINs split across one box per character. The interactive root: it owns the value as a per-cell `string[]` (controlled via `v-model` / `update:modelValue` or uncontrolled via `defaultValue`), sizes the field to `length`, enforces the `type` ('text' | 'number') and `mask`, and provides context to each `PinInputInput`. Emits `complete` once every cell is filled. Use it for verification codes where each character gets its own cell with auto-advance, arrow-key navigation, and clipboard paste spreading across cells. Native form support: pass `name` (and optionally `required` / `id`) to render a visually-hidden form control holding the joined value, so the field submits with its owning `<form>` and participates in native `required` validation.
| Prop | Type | Description |
|---|---|---|
defaultValue? | string[] | — |
length? | number | — |
mask? | boolean | — |
otp? | boolean | — |
type? | 'text' | 'number' | — |
disabled? | boolean | — |
placeholder? | string | — |
dir? | Direction | Reading direction. Affects arrow-key navigation (in rtl, ArrowRight
moves to the previous cell). Falls back to the active ConfigProvider
dir, then ltr. |
name? | string | Name submitted with the owning form (enables the hidden form control). |
required? | boolean | Mirror the required constraint so native form validation fires. |
id? | string | Id forwarded to the hidden form control for label association. |
modelValue? | string[] | Two-way bound value (v-model). |
| Event | Payload |
|---|---|
complete | [value: string] |
update:modelValue | [value: string[]] |