PinInput
A fixed-length code, one box per character, read back as a single value.
A verification code is one value typed in several places. PinInput gives it a box per
character, then packs what it collects into one dense string, so a consumer reads value and
never has to reason about positions: deleting a character in the middle moves the ones after it
left rather than leaving a gap.
It owns typing, focus and paste, and nothing else. The code is a value the consumer verifies; no authentication logic lives here.
Usage
import { PinInput } from "@asheeui/web";
<PinInput
label="Verification code"
description="The four digits we sent to your phone."
onComplete={verify}
/>
The field is controlled when value is given and owns the code otherwise, starting from
defaultValue. onValueChange reports every change; onComplete fires with the whole code once
it reaches its length, so a screen can verify from either. With a name, the field also renders
one hidden input carrying the whole code, which is what lets a plain form post a single field
rather than four.
Length, mode and grouping
length and mode are the field's two rules about what it collects. numeric keeps digits,
alphanumeric keeps letters and digits, and text keeps anything but whitespace; the filter runs
on typing and on paste alike, so dropping a whole code in fills the boxes with the characters the
mode accepts and drops the rest.
separatorAfter draws a dash after a given box, which is how a six-digit code reads as two
groups of three. It is presentational: the dash is hidden from assistive technology, and it is
ignored when it would fall after the last box.
Masking and failure
masked makes each box a password field, for a code that must not be readable over a shoulder.
The edge of the boxes and the tone of the message are two separate props: isInvalid colours the
boxes and marks them invalid, and status decides the tone of the message below them.
That separation is the point. A code that is being checked is not a code that failed, so a field
can report status="default" with a message while leaving the boxes neutral, and can mark a
refused code without also claiming the reader typed something wrong in the field's own
vocabulary. isDisabled dims the boxes and stops the field accepting input while the label, the
description and the message stay readable.
Props
length, mode, size, masked, isDisabled and isInvalid are registered by the component
as 4, numeric, md, false, false and false. radius is not registered: it resolves
from the application's defaultRadius, so the boxes round with the rest of the form. When the
application states no radius, the field's own fallback is sm, tighter than a text field's
md, because a box the size of a character should not be a pill.
groupLabel defaults to Verification code. It names the group when the field has no visible
label, and it prefixes the name of every box.
Accessibility
- The boxes are one
<fieldset>. With alabel, the group is named byaria-labelledbypointing at the visible label; without one it takesgroupLabelasaria-label. The component never moves focus by itself, the ref is the group, so a screen that knows why can focus it. - Every box names itself with its position,
Verification code, character 2 of 4, because four one-character inputs are otherwise announced as four anonymous fields. - Typing a character advances to the next box, Backspace clears the box and steps
back, Delete clears without moving, and the arrow keys, Home and
End move without changing the code. A pasted code fills from the caret onward, and
the first box asks the platform for
one-time-codeautofill. isInvalidmarks every boxaria-invalidand colours its edge, so a refused code is never signalled by colour alone;status="error"puts the message in a live region.
The label, description and message behave as the field anatomy describes.