One-time-code and PIN entry split across single-character fields with paste distribution.
|
|
| Package |
@vizejs/ui/pin-input |
| Maturity |
stable |
| Own the source |
vize lib pull pin-input |
| Requires |
context, controllable-state, id |
| Aliases |
otp input, one-time code, verification code, pin code, segmented input |
| Covers |
HTML autocomplete one-time-code, Chakra PinInput, Reka UI PinInput |
Usage
import { PinInput, PinInputField } from "@vizejs/ui/pin-input";
Or copy the source into your project with vize lib pull pin-input (see Source Distribution).
API
Source: src/families/form/pin-input/pin-input.vue
Props
| Prop |
Type |
Default |
Description |
length (required) |
Length |
required |
Number of fields. A literal length types the complete characters tuple. |
modelValue |
string |
undefined |
Controlled code. undefined selects uncontrolled behavior. |
defaultValue |
string |
"" |
Initial uncontrolled code, also restored by form reset. |
type |
PinInputType |
"numeric" |
Accepted characters: decimal digits, or letters and digits. |
pattern |
RegExp |
undefined |
Single-character pattern that overrides type, for example /[0-9A-F]/. |
mask |
boolean |
false |
Render fields as password inputs so entered characters are masked. |
otp |
boolean |
true |
One-time-code mode: the first field advertises autocomplete="one-time-code" so browsers can autofill SMS codes. |
id |
string | null |
undefined |
Base id; fields use <id>-<index>. null and undefined select a deterministic fallback. |
name |
string |
undefined |
Native form field name. A hidden input submits the joined code. |
form |
string |
undefined |
Id of a form owner outside the component tree. |
placeholder |
string |
undefined |
Placeholder shown in each empty field, for example "○". |
disabled |
boolean |
false |
Disable every field and native form submission. |
required |
boolean |
false |
Require every field for native constraint validation. |
getFieldLabel |
(index: number, length: number) => string |
(index, length) => Character ${index + 1} of ${length} |
Accessible name of each field. |
ariaLabel |
string |
undefined |
Accessible name of the group when no label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the group. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe every field. |
ariaErrormessage |
string |
undefined |
Id of the validation error message used while invalid. |
ariaInvalid |
PinInputAriaInvalid |
false |
Invalid state announced on every field. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: string] |
Fired when the joined code requests a new controlled value. |
complete |
[value: string, characters: PinInputCharacters<Length>] |
Fired when the last field is filled, with the code and its characters as a typed tuple. |
Slots
| Slot |
Slot props |
Description |
default |
PinInputSlotState |
Renders one PinInputField per entry in indexes, plus any separators. |
Exposed
| Member |
Type |
Description |
value |
string |
Entered characters joined, without gaps. |
length |
number |
Number of fields. |
indexes |
readonly number[] |
Field indexes to render, [0, 1, ..., length - 1]. |
complete |
boolean |
Whether every field is filled. |
disabled |
boolean |
Whether the fields are disabled. |
state |
PinInputState |
Stable state token. |
root |
HTMLDivElement | null |
Rendered group element. |
focus |
(index?: number) => void |
Focus a field (default: the first empty one). |
setValue |
(value: string) => boolean |
Replace the code (filtered and truncated); returns whether it changed. |
clear |
() => boolean |
Clear every field; returns whether the value changed. |
Source: src/families/form/pin-input/pin-input-field.vue
Props
| Prop |
Type |
Default |
Description |
index (required) |
number |
required |
Zero-based position of this field in the code. |
ariaLabel |
string |
undefined |
Accessible name overriding the group's getFieldLabel. |
Behavior
Normative state x input -> outcome table for pin-input.vue and
pin-input-field.vue (@vizejs/ui/pin-input). The model is the joined code
(no gaps); a literal length types the complete characters tuple. Every row
is proven by the named test in pin-input.test.ts or pin-input-ssr.test.ts;
compile-only assertions live in pin-input.types.test-d.ts.
| # |
State |
Input |
Outcome |
Proven by |
| PI1 |
helpers |
sanitize / write / remove / narrow |
accepted characters are filtered (NFKC folds full-width), writes clamp to the end and truncate, removals shift left, tuples narrow by length |
sanitizes, writes, removes, and narrows code characters |
| PI2 |
named, seeded |
render |
labelled role="group"; fields with deterministic ids, one-time-code autocomplete on the first field, inputmode, per-field labels, hidden joined value |
renders a labelled group of one-time-code fields with form hooks |
| PI3 |
empty |
typing |
characters fill the first gap, focus advances, rejects are ignored, complete(value, tuple) fires once, typing over a field replaces it |
typing fills fields left to right, advances focus, and emits a typed completion |
| PI4 |
any |
paste / SMS autofill |
pasted or autofilled codes are filtered and distributed across fields; focus lands on the last written field |
paste and autofill distribute characters across fields |
| PI5 |
filled |
Backspace / Delete / arrows / Home / End |
Backspace clears the focused character or deletes backwards from an empty field; Delete removes forward; arrows/Home/End move focus; Tab stays native |
Backspace, Delete, arrows, Home, and End edit and move between fields |
| PI6 |
options |
mask / alphanumeric / pattern / controlled |
mask renders password fields, type="alphanumeric" uses a text keyboard, pattern overrides accepted characters, controlled values win |
masking, alphanumeric codes, custom patterns, and controlled values |
| PI7 |
in a form |
submit / reset |
hidden input submits the joined code; fields are required only until complete; form reset restores defaultValue |
submits the joined code, requires completion, and restores defaults on reset |
| PI8 |
disabled |
edits |
every field is natively disabled and edits are ignored |
disabled codes disable every field and ignore edits |
| PI9 |
imperative |
expose / missing provider |
focus (first empty field by default), setValue, clear, state; fields outside a PinInput throw VIZE_UI_CONTEXT_MISSING |
exposes focus, setValue, clear, and state; fields need a PinInput |
| PI10 |
SSR / hydration |
isolated requests |
byte-identical markup, hydration without diagnostics, and interactive fields |
renders byte-identical code fields and hydrates without mismatches |
Public extension contract
| Surface |
Contract |
| Parts |
root (group), field (one native input per character). |
| Data attributes |
data-vize-ui, root data-state/data-complete; field data-index/data-filled. |
| Slots |
Root default slot receives PinInputSlotState; render a PinInputField per indexes entry. |
The subpath is tree-shakable and ships no CSS; those package contracts are
pinned by distribution.test.ts, check:size, and check:tree-shaking.