# Input OTP

Accessible one-time password input with grouped slots, copy-paste distribution, roving focus, signal-friendly control, and reactive-form support.

Use Input OTP for verification codes, step-up authentication, banking PIN entry, and short recovery codes where each character should be visually separated without losing keyboard or paste ergonomics.

## Import

```ts
import {
  InputOtpComponent,
  InputOtpGroupComponent,
  InputOtpSeparatorComponent,
  InputOtpSlotComponent,
  REGEXP_ONLY_DIGITS,
  REGEXP_ONLY_DIGITS_AND_CHARS,
} from '@edsis/component/input-otp';
```

When the code input belongs to a richer field layout, pair it with the existing form primitives:

```ts
import {
  FormDescriptionComponent,
  FormFieldComponent,
  FormLabelComponent,
  FormMessageComponent,
} from '@edsis/component/form';
```

## Composition

The Angular structure follows the shadcn Input OTP information architecture while translating it to standalone selectors.

```text
InputOtp
├── InputOtpGroup
│   ├── InputOtpSlot
│   ├── InputOtpSlot
│   └── InputOtpSlot
├── InputOtpSeparator
└── InputOtpGroup
    ├── InputOtpSlot
    ├── InputOtpSlot
    └── InputOtpSlot
```

## Basic Usage

Use the root component for value flow and the projected parts for layout.

```ts
protected readonly code = signal('');
protected readonly digitsPattern = REGEXP_ONLY_DIGITS;
```

```html
<FormField>
  <FormLabel>Verification code</FormLabel>
  <InputOtp
    [maxLength]="6"
    [pattern]="digitsPattern"
    [value]="code()"
    (valueChange)="code.set($event)"
  >
    <InputOtpGroup>
      <InputOtpSlot [index]="0" />
      <InputOtpSlot [index]="1" />
      <InputOtpSlot [index]="2" />
      <InputOtpSlot [index]="3" />
      <InputOtpSlot [index]="4" />
      <InputOtpSlot [index]="5" />
    </InputOtpGroup>
  </InputOtp>
  <FormDescription>Paste the code from your email or authenticator app.</FormDescription>
</FormField>
```

## Common Patterns

### Separator groups

Split longer codes into smaller clusters when the verification flow benefits from visual chunking.

```html
<InputOtp
  [maxLength]="6"
  [pattern]="digitsPattern"
  [value]="code()"
  (valueChange)="code.set($event)"
>
  <InputOtpGroup>
    <InputOtpSlot [index]="0" />
    <InputOtpSlot [index]="1" />
  </InputOtpGroup>
  <InputOtpSeparator />
  <InputOtpGroup>
    <InputOtpSlot [index]="2" />
    <InputOtpSlot [index]="3" />
  </InputOtpGroup>
  <InputOtpSeparator />
  <InputOtpGroup>
    <InputOtpSlot [index]="4" />
    <InputOtpSlot [index]="5" />
  </InputOtpGroup>
</InputOtp>
```

### Reactive forms

The root component implements `ControlValueAccessor` and also integrates directly with `FormField`, so labels, descriptions, and invalid-state wiring work without `FormControl`.

```ts
readonly verificationForm = new FormGroup({
  code: new FormControl('', {
    nonNullable: true,
    validators: [Validators.required, Validators.minLength(6)],
  }),
});
```

```html
<FormField>
  <FormLabel>Verification code</FormLabel>
  <InputOtp formControlName="code" [maxLength]="6" [pattern]="digitsPattern">
    <InputOtpGroup
      class="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl"
    >
      <InputOtpSlot [index]="0" />
      <InputOtpSlot [index]="1" />
      <InputOtpSlot [index]="2" />
    </InputOtpGroup>
    <InputOtpSeparator class="mx-2" />
    <InputOtpGroup
      class="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl"
    >
      <InputOtpSlot [index]="3" />
      <InputOtpSlot [index]="4" />
      <InputOtpSlot [index]="5" />
    </InputOtpGroup>
  </InputOtp>
  <FormDescription>Enter the 6-digit code sent to m@example.com.</FormDescription>
  <FormMessage />
</FormField>
```

### Disabled and invalid states

Use the native `disabled` input when the code should remain visible but unavailable, and forward `aria-invalid="true"` when an expired or rejected code needs the destructive treatment.

```html
<InputOtp [maxLength]="6" [pattern]="digitsPattern" value="123456" disabled>
  <InputOtpGroup>
    <InputOtpSlot [index]="0" />
    <InputOtpSlot [index]="1" />
    <InputOtpSlot [index]="2" />
  </InputOtpGroup>
  <InputOtpSeparator />
  <InputOtpGroup>
    <InputOtpSlot [index]="3" />
    <InputOtpSlot [index]="4" />
    <InputOtpSlot [index]="5" />
  </InputOtpGroup>
</InputOtp>
```

```html
<InputOtp
  aria-invalid="true"
  [maxLength]="6"
  [pattern]="digitsPattern"
  [value]="invalidCode()"
  (valueChange)="invalidCode.set($event)"
>
  <InputOtpGroup>
    <InputOtpSlot [index]="0" />
    <InputOtpSlot [index]="1" />
    <InputOtpSlot [index]="2" />
  </InputOtpGroup>
  <InputOtpSeparator />
  <InputOtpGroup>
    <InputOtpSlot [index]="3" />
    <InputOtpSlot [index]="4" />
    <InputOtpSlot [index]="5" />
  </InputOtpGroup>
</InputOtp>
```

### Four digits and alphanumeric codes

Use `REGEXP_ONLY_DIGITS` for numeric OTPs and `REGEXP_ONLY_DIGITS_AND_CHARS` for mixed recovery codes.

```html
<InputOtp [maxLength]="4" [pattern]="digitsPattern" [inputmode]="'numeric'">
  <InputOtpGroup>
    <InputOtpSlot [index]="0" />
    <InputOtpSlot [index]="1" />
    <InputOtpSlot [index]="2" />
    <InputOtpSlot [index]="3" />
  </InputOtpGroup>
</InputOtp>
```

```html
<InputOtp [maxLength]="6" [pattern]="digitsAndCharsPattern" [inputmode]="'text'">
  <InputOtpGroup>
    <InputOtpSlot [index]="0" />
    <InputOtpSlot [index]="1" />
    <InputOtpSlot [index]="2" />
  </InputOtpGroup>
  <InputOtpSeparator />
  <InputOtpGroup>
    <InputOtpSlot [index]="3" />
    <InputOtpSlot [index]="4" />
    <InputOtpSlot [index]="5" />
  </InputOtpGroup>
</InputOtp>
```

## API Reference

### `InputOtpComponent`

| Input              | Type                       | Default           |
| ------------------ | -------------------------- | ----------------- |
| `maxLength`        | `number`                   | `6`               |
| `value`            | `string`                   | `undefined`       |
| `pattern`          | `RegExp \| string \| null` | `null`            |
| `required`         | `boolean`                  | `false`           |
| `disabled`         | `boolean`                  | `false`           |
| `inputmode`        | `string \| null`           | `'numeric'`       |
| `autocomplete`     | `string \| null`           | `'one-time-code'` |
| `aria-label`       | `string \| null`           | `null`            |
| `aria-labelledby`  | `string \| null`           | `null`            |
| `aria-describedby` | `string \| null`           | `null`            |
| `class`            | `string`                   | `''`              |

### Output

| Output        | Type     |
| ------------- | -------- |
| `valueChange` | `string` |

### Parts

| Part                         | Selector            | Notes                                                         |
| ---------------------------- | ------------------- | ------------------------------------------------------------- |
| `InputOtpGroupComponent`     | `InputOtpGroup`     | Groups adjacent slots into one bordered cluster.              |
| `InputOtpSlotComponent`      | `InputOtpSlot`      | Requires an `index` and renders the focusable character slot. |
| `InputOtpSeparatorComponent` | `InputOtpSeparator` | Inserts an optional visual separator between groups.          |

Convenience exports: `REGEXP_ONLY_DIGITS` and `REGEXP_ONLY_DIGITS_AND_CHARS`.

## Styling and Theming

The component exposes shadcn-style `data-slot` hooks on the root, group, separator, and slot hosts. Use `*:data-[slot=input-otp-slot]` utilities on `InputOtpGroup` when a layout needs taller or wider cells, for example:

```html
<InputOtpGroup
  class="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl"
>
  ...
</InputOtpGroup>
```

The default styles use the shared border, ring, destructive, foreground, and muted-foreground tokens from the library theme.

## Accessibility

- Each slot is a real input with an accessible name that includes its position in the sequence.
- `FormField` labels and descriptions wire to the component automatically when the OTP lives inside the field wrapper.
- `aria-invalid="true"` on the root forwards the destructive treatment across the slot set.
- Keep the surrounding label and helper text descriptive. Users should understand where the code came from and how many characters are expected.

## Keyboard Interactions

- Typing a character replaces the current slot and advances focus.
- Paste distributes accepted characters from the current slot onward.
- Arrow keys move across slots and flip direction under RTL layouts.
- `Backspace` removes the current or previous slot and shifts the remaining value left.
- `Delete` removes the current slot and shifts the remaining value left.

## Angular Notes

- Use `[value]` plus `(valueChange)` for signal-backed local state.
- Use `formControlName`, `[formControl]`, or `[(ngModel)]` for form-driven flows.
- There is no separate `defaultValue` input; seed the initial code with the bound signal or form control value.
- Unlike native inputs, this component does not need `FormControl`; it integrates with the form-field context directly.

## Source Parity

This Angular slice keeps the shadcn composition, grouped separators, pattern filtering, paste handling, and verification-form examples. The deliberate Angular deviation is the runtime implementation: each slot is a visible input coordinated by the root CVA instead of the upstream single hidden-input package. Browser one-time-code autofill works best through the first slot with the default `autocomplete="one-time-code"` behavior.
