A single component for one-time passwords, PIN entry, and segmented license keys. Same API covers [1,1,1,1,1,1] classic OTP and non-uniform layouts like MS Office's [6,6,4,4,6,6].
<bs-otp-input
[(ngModel)]="code"
(complete)="onComplete($event)"
autofocus>
</bs-otp-input>
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BsOtpInputComponent } from '@mintplayer/ng-bootstrap/otp-input';
@Component({
selector: 'my-otp',
templateUrl: './my-otp.component.html',
imports: [FormsModule, BsOtpInputComponent],
})
export class MyOtpComponent {
readonly code = signal<string | undefined>(undefined);
onComplete(code: string): void {
// Fires once when all 6 boxes are filled. Wire to auto-submit.
this.submit(code);
}
private submit(code: string): void { /* … */ }
}
<!-- Non-uniform layouts via [groups]. Example: MS Office product key
is 6-6-4-4-6-6 = 32 chars. Paste with dashes — the component
strips separators and fills correctly. -->
<bs-otp-input
[groups]="[6, 6, 4, 4, 6, 6]"
type="alphanumeric"
letterCase="upper"
size="lg"
[(ngModel)]="productKey">
</bs-otp-input>
// Reactive Forms: bind via [formControl] for validators + touched state.
readonly otpControl = new FormControl<string>('', {
nonNullable: true,
validators: [Validators.required, Validators.minLength(6)],
});
<bs-otp-input [formControl]="otpControl"></bs-otp-input>
Default layout. Template-driven binding via [(ngModel)]. The (complete) event fires once when all 6 boxes are filled — wire it to auto-submit. SMS autofill works on iOS / Android (the hidden input has autocomplete="one-time-code").
[groups]="[1,1,1,1]" with type="password". The most-recently typed character is visible for 700 ms then masks to •. Paste never reveals.
Current value (for demo):
<!-- Uniform 4-digit PIN, masked. Most-recent character visible for
~700 ms, then masks to •. Paste never reveals. -->
<bs-otp-input
[groups]="[1, 1, 1, 1]"
type="password"
[(ngModel)]="pinCode">
</bs-otp-input>
[groups]="[6,6,4,4,6,6]", type="alphanumeric", letterCase="upper". Paste a string with separators (e.g. "AB1234-CD5678-EF12-GH34-IJ5678-KL9012") — the component strips the dashes and fills correctly.
Current value:
[groups]="[5,5,5,5,5]", type="alphanumeric".
Current value:
<!-- Windows product key (uniform 5-5-5-5-5 = 25 chars). -->
<bs-otp-input
[groups]="[5, 5, 5, 5, 5]"
type="alphanumeric"
[(ngModel)]="productKey">
</bs-otp-input>
<!-- size accepts 'sm' | 'md' | 'lg' -->
<bs-otp-input size="sm"></bs-otp-input>
<bs-otp-input size="md"></bs-otp-input>
<bs-otp-input size="lg"></bs-otp-input>
Bound to a FormControl with Validators.required and Validators.minLength(6). The red border appears once the field is touched and the value is still invalid — mirrors Bootstrap's .is-invalid convention.
Status: INVALID · Touched: false · Value: