# PinField Guides

## 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'` on `PinFieldInput` so 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

```tsx
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')));
  };

  return (
    <form
      onSubmit={onSubmit}
      onReset={() => setSubmitted('')}
      style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)', width: '100%', maxWidth: 320 }}
    >
      <Label htmlFor={id}>Code from the SMS sent to +1 ••• ••• 0132</Label>
      <PinField maxLength={6}>
        <PinFieldInput id={id} name='otp' autoComplete='one-time-code' required pattern='\d{6}' />
        <PinFieldSlot index={0} />
        <PinFieldSlot index={1} />
        <PinFieldSlot index={2} />
        <PinFieldSlot index={3} />
        <PinFieldSlot index={4} />
        <PinFieldSlot index={5} />
      </PinField>
      <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 'var(--spacing-4)', marginBlockStart: 'var(--spacing-9)' }}>
        <Button type='reset' variant='secondary' appearance='ghost'>Clear</Button>
        <Button type='submit'>Verify</Button>
      </div>
      {submitted && (
        <Text size={2} role='status' style={{ color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums', marginBlockStart: 'var(--spacing-4)' }}>
          Submitted otp={submitted}
        </Text>
      )}
    </form>
  );
}
```

### 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 `PinFieldSeparator` only for visual grouping
- keep actual input length consistent

Trade-offs:

- mirrors real-world code formatting
- adds one more layout decision

```tsx
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} />
        <PinFieldSlot index={1} />
        <PinFieldSlot index={2} />
        <PinFieldSeparator />
        <PinFieldSlot index={3} />
        <PinFieldSlot index={4} />
        <PinFieldSlot index={5} />
      </PinField>
    </div>
  );
}
```

### Alphanumeric Activation Code

When to use:

- the code accepts letters and digits

Composition notes:

- pass a matching mask
- normalize case in `onChangeValue` when 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

```tsx
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]+$/}
        maxLength={5}
        value={code}
        onChangeValue={(next) => setCode(next.toUpperCase())}
      >
        <PinFieldInput id={id} inputMode='text' autoCapitalize='characters' autoComplete='off' />
        <PinFieldSlot index={0} />
        <PinFieldSlot index={1} />
        <PinFieldSlot index={2} />
        <PinFieldSlot index={3} />
        <PinFieldSlot index={4} />
      </PinField>
      <Text size={2} style={{ color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums' }}>
        {code.length === 5 ? `Ready to activate ${code}.` : `${5 - code.length} characters left.`}
      </Text>
    </div>
  );
}
```

## 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 `PinFieldInput` is 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.
