OTP / segmented-code input

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].

Basic usage

  
    
      
        <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 { /* … */ }
        
    
      
        }
        
    

Grouped (license key)

  
    
      
        <!-- 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

  
    
      
        // 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>
        
    

Classic 6-digit OTP

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").


4-digit PIN (password type)

[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>
        
    

MS Office product key (non-uniform groups)

[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:


Windows product key (uniform 5-5-5-5-5)

[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 variants

sm
md
lg
  
    
      
        <!-- 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>
        
    

Reactive forms — invalid state

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: