Components Forms
PinInput
StableA one-time code or PIN field split into cells, with paste, SMS autofill and full keyboard support.
Use PinInput for short codes of a known length: email and SMS verification codes, two-factor codes and PINs. It behaves as one field, so typing advances, Backspace steps back and a pasted or autofilled code fills every cell. For longer or variable-length values use Input.
Overview
Enter the 6-digit code we sent to priya.nair@binds.si.
import { FormField, PinInput } from '@bindshq/stance-react';
export function Basic() {
return (
<FormField
label="Verification code"
description="Enter the 6-digit code we sent to priya.nair@binds.si."
>
<PinInput name="code" />
</FormField>
);
}Light, dark and high contrast
Every Stance component is designed and tested in all three appearance modes.
Enter the 6-digit code we sent to priya.nair@binds.si.
Enter the 6-digit code we sent to priya.nair@binds.si.
Enter the 6-digit code we sent to priya.nair@binds.si.
Import
import { PinInput } from '@bindshq/stance-react';Playground
<PinInput />Examples
import { PinInput } from '@bindshq/stance-react';
export function Sizes() {
return (
<div style={{ display: 'grid', gap: 16 }}>
<PinInput aria-label="Small code" size="sm" length={4} defaultValue="12" />
<PinInput aria-label="Medium code" size="md" length={4} defaultValue="12" />
<PinInput aria-label="Large code" size="lg" length={4} defaultValue="12" />
</div>
);
}Masked PIN
mask hides the characters, for PINs entered in view of others.
Your 4-digit PIN confirms each signature.
import { FormField, PinInput } from '@bindshq/stance-react';
export function Masked() {
return (
<FormField label="Signing PIN" description="Your 4-digit PIN confirms each signature.">
<PinInput length={4} mask oneTimeCode={false} />
</FormField>
);
}Letters and numbers, 8 characters.
import { FormField, PinInput } from '@bindshq/stance-react';
export function Alphanumeric() {
return (
<FormField label="Recovery code" description="Letters and numbers, 8 characters.">
<PinInput length={8} type="alphanumeric" size="sm" oneTimeCode={false} />
</FormField>
);
}Validation
onComplete fires once every cell is filled, so the code can be checked straight away.
Try 246810.
import * as React from 'react';
import { Button, FormField, PinInput } from '@bindshq/stance-react';
export function Validation() {
const [code, setCode] = React.useState('');
const [status, setStatus] = React.useState<'idle' | 'wrong' | 'ok'>('idle');
return (
<div style={{ display: 'grid', gap: 12, justifyItems: 'start' }}>
<FormField
label="Verification code"
description="Try 246810."
error={status === 'wrong' ? 'That code is not correct. Check it and try again.' : undefined}
>
<PinInput
value={code}
onChange={(v) => {
setCode(v);
setStatus('idle');
}}
onComplete={(v) => setStatus(v === '246810' ? 'ok' : 'wrong')}
/>
</FormField>
<p role="status" style={{ margin: 0 }}>
{status === 'ok' ? 'Email address verified.' : ''}
</p>
<Button variant="link" onClick={() => setCode('')}>
Send a new code
</Button>
</div>
);
}API reference
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of cells. |
value | string | None | Controlled value. |
defaultValue | string | None | Initial value when uncontrolled. |
onChange | (value: string) => void | None | Called with the joined value on every change. |
onComplete | (value: string) => void | None | Called once all cells are filled. |
type | 'numeric' | 'alphanumeric' | 'numeric' | Accepted characters. Numeric shows the number keypad on phones. |
mask | boolean | false | Hide the characters. |
size | 'sm' | 'md' | 'lg' | 'md' | Cell size: 32, 40 or 48px. |
name | string | None | Adds a hidden input carrying the joined value for form submission. |
oneTimeCode | boolean | true | Sets autocomplete="one-time-code" on the first cell for SMS and email code autofill. |
invalid | boolean | None | Marks the field invalid. Set automatically inside an erroring FormField. |
disabled | boolean | false | Disables every cell. |
readOnly | boolean | false | Shows the value without allowing changes. |
autoFocus | boolean | false | Focus the first cell on mount. |
getCellLabel | (index: number, length: number) => string | None | Custom accessible name for each cell. |
Accessibility
- The cells form a group named by the FormField label (or aria-label), with the description and error linked to it.
- Each cell has its own name, such as “Digit 1 of 6”, so screen-reader users always know their position.
- The first cell uses autocomplete="one-time-code" and accepts a whole code, so SMS and password-manager autofill work.
- Pasting a code fills every cell and ignores spaces and dashes. Never block paste on a verification code (WCAG 3.3.8).
- Numeric codes use inputmode="numeric" to show the number keypad without the drawbacks of type="number".
- Clicking the FormField label focuses the first cell.
Keyboard
| Keys | Action |
|---|---|
| 0 to 9 A to Z | Enters a character and moves to the next cell. |
| Backspace | Clears the current cell, or steps back and clears the previous one. |
| Delete | Clears the current cell. |
| ← → | Moves between cells. |
| Home End | Moves to the first cell or the next empty one. |
| Ctrl/⌘ V | Pastes a whole code. |
Usage guidelines
Do
- Say where the code was sent and offer a way to resend it.
- Verify on completion, and keep the code in place if it is wrong so it can be corrected.
Don’t
- Use it for values of unknown length such as passwords or phone numbers.
- Clear all cells on an error; a single mistyped digit should be quick to fix.