Input OTP
A one-time code input split into slots, with paste support.
import { … } from '@inlaylab/ivorie/input-otp'import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IV_INPUT_OTP_IMPORTS } from '@inlaylab/ivorie/input-otp';
@Component({
selector: 'demo-input-otp',
imports: [IV_INPUT_OTP_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<iv-input-otp maxLength="6" aria-label="One-time code">
<iv-input-otp-group>
<iv-input-otp-slot index="0" />
<iv-input-otp-slot index="1" />
<iv-input-otp-slot index="2" />
</iv-input-otp-group>
<iv-input-otp-separator />
<iv-input-otp-group>
<iv-input-otp-slot index="3" />
<iv-input-otp-slot index="4" />
<iv-input-otp-slot index="5" />
</iv-input-otp-group>
</iv-input-otp>
`,
})
export default class InputOtpDemo {}
Installation
npx ivorie add input-otpimport { … } from '@inlaylab/ivorie/input-otp'One hidden input holds the value. The slots only show it, so typing, paste and autofill work like a normal input.
Usage
import { IV_INPUT_OTP_IMPORTS } from '@inlaylab/ivorie/input-otp';<iv-input-otp maxLength="4" aria-label="Code" [(ngModel)]="code">
<iv-input-otp-group>
<iv-input-otp-slot index="0" />
<iv-input-otp-slot index="1" />
<iv-input-otp-slot index="2" />
<iv-input-otp-slot index="3" />
</iv-input-otp-group>
</iv-input-otp>Examples
Pattern
pattern is a regex source. Input that does not match is dropped. REGEXP_ONLY_DIGITS and REGEXP_ONLY_DIGITS_AND_CHARS are exported.
Digits only.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IV_INPUT_OTP_IMPORTS, REGEXP_ONLY_DIGITS } from '@inlaylab/ivorie/input-otp';
@Component({
selector: 'demo-input-otp-pattern',
imports: [IV_INPUT_OTP_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-2">
<iv-input-otp maxLength="4" aria-label="PIN" [pattern]="digits" (complete)="done.set($event)">
<iv-input-otp-group>
<iv-input-otp-slot index="0" />
<iv-input-otp-slot index="1" />
<iv-input-otp-slot index="2" />
<iv-input-otp-slot index="3" />
</iv-input-otp-group>
</iv-input-otp>
<p class="text-muted-foreground text-sm">{{ done() ? 'Entered ' + done() : 'Digits only.' }}</p>
</div>
`,
})
export default class InputOtpPatternDemo {
readonly digits = REGEXP_ONLY_DIGITS;
readonly done = signal('');
}
Reactive forms
Value: "" · invalid
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { IV_INPUT_OTP_IMPORTS } from '@inlaylab/ivorie/input-otp';
@Component({
selector: 'demo-input-otp-reactive-forms',
imports: [IV_INPUT_OTP_IMPORTS, ReactiveFormsModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col items-center gap-2">
<iv-input-otp maxLength="6" aria-label="Verification code" [formControl]="code">
<iv-input-otp-group>
@for (i of slots; track i) {
<iv-input-otp-slot [index]="i" />
}
</iv-input-otp-group>
</iv-input-otp>
<p class="text-muted-foreground text-sm">Value: "{{ code.value }}" · {{ code.valid ? 'valid' : 'invalid' }}</p>
</div>
`,
})
export default class InputOtpReactiveFormsDemo {
readonly slots = [0, 1, 2, 3, 4, 5];
readonly code = new FormControl('', [Validators.required, Validators.minLength(6)]);
}
API
Every part also accepts class.
iv-input-otp
| Name | Type | Default | Description |
|---|---|---|---|
maxLength |
number |
required | Number of characters. |
pattern |
string | null |
null |
Regex source each value must match. |
value |
string |
'' |
Value, when not used with forms. |
disabled |
boolean |
false |
Disables the input. Form-disabled works too. |
id |
string | null |
null |
Id of the hidden input, for a <label for>. |
aria-label / aria-labelledby |
string | null |
null |
Label for the input. |
Outputs: valueChange (string), complete (string, once every slot is filled).
Works with ngModel and reactive forms.
iv-input-otp-slot
| Name | Type | Default | Description |
|---|---|---|---|
index |
number |
required | Which character the slot shows. |
Other parts
iv-input-otp-group,iv-input-otp-separator.
Accessibility
The real input carries the label and value, so screen readers read it as one text field.