Patterns
OTP Verification
When to use:
- users enter a short verification code from SMS, email, or authenticator apps
Composition notes:
- keep the length fixed
- set
autoComplete='one-time-code'onPinFieldInputso the browser can offer the code - place helper or error copy nearby
Trade-offs:
- easy to scan and correct
- more specialized than a standard text field
import { Button, Label, PinField, PinFieldInput, PinFieldSlot, Text } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
export default function Example6DigitOtp() {
const id = useId();
const [submitted, setSubmitted] = useState('');
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setSubmitted(String(new FormData(event.currentTarget).get('otp')));
};
Grouped Legacy Code
When to use:
- the code is visually grouped in the product or in the external source users copy from
Composition notes:
- use
PinFieldSeparatoronly for visual grouping - keep actual input length consistent
Trade-offs:
- mirrors real-world code formatting
- adds one more layout decision
import { Label, PinField, PinFieldInput, PinFieldSeparator, PinFieldSlot } from '@vega-ui/react';
import { useId } from 'react';
export default function GroupedCodeWithSeparator() {
const id = useId();
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 320 }}>
<Label htmlFor={id}>Code from your authenticator app</Label>
<PinField maxLength={6}>
<PinFieldInput id={id} name='totp' autoComplete='one-time-code' />
<PinFieldSlot index={0} />Alphanumeric Activation Code
When to use:
- the code accepts letters and digits
Composition notes:
- pass a matching mask
- normalize case in
onChangeValuewhen the backend expects capitals - align the frontend mask with backend validation
Trade-offs:
- flexible
- easier to drift from backend expectations if the mask is guessed
import { Label, PinField, PinFieldInput, PinFieldSlot, Text } from '@vega-ui/react';
import { useId, useState } from 'react';
export default function AlphanumericActivationCode() {
const id = useId();
const [code, setCode] = useState('');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 320 }}>
<Label htmlFor={id}>Activation code from the box</Label>
<PinField
mask={/^[A-Za-z0-9]+$/}Comparison
Quick Decision Rule
Use PinField for short fixed-length codes that benefit from segmented display. Use PasswordField for secret strings and TextField for general text.
PinField vs PasswordField
Use PinField when:
- the value is an OTP or short verification code
Use PasswordField when:
- the value is a longer secret string with reveal or hide behavior
PinField vs TextField
Use PinField when:
- segmentation improves readability and entry
Use TextField when:
- the field is ordinary free-form input
Choose This Component When
- code length is fixed and short
- segmented visual entry helps scanning and correction
- paste and auto-advance behavior are part of the expected UX
Do Not Choose This Component When
- the value is free-form
- the field needs generic text editing rather than slot-based entry
Troubleshooting
Typing Does Not Update The Visible Slots
Symptom
The segmented UI renders, but typed characters do not appear in the slots.
Likely Cause
PinFieldInput is missing from the composition.
How To Verify
- inspect the rendered composition
- confirm
PinFieldInputis present inside the root
Fix
Add PinFieldInput inside PinField.
Slot Order Feels Wrong Or Caret Movement Is Confusing
Symptom
Arrow keys or typing appear to target the wrong slot.
Likely Cause
Slot indexes are not rendered in ascending order.
How To Verify
- inspect the slot sequence
- compare it with
maxLength
Fix
Render PinFieldSlot items in ascending index order and keep them aligned with the intended code length.
The Backend Rejects Codes That Look Valid In The UI
Symptom
Users can type the code, but submission fails due to format mismatch.
Likely Cause
The frontend mask does not match backend expectations.
How To Verify
- compare the current mask with the accepted backend format
Fix
Update the mask and placeholders so they represent the real accepted code format.