ngx-otp-input is a CVA-first Angular OTP component with a boxed UI, mobile-friendly behavior, and accessibility-focused defaults.
Building OTP inputs from scratch usually means solving many edge cases:
- paste and autofill behavior across browsers and mobile keyboards
- accessible announcements and clear focus handling
- validation, sanitization, and keyboard navigation
- a polished OTP box UI that still works with Angular Forms
This library handles those details and exposes a small, explicit API.
- Live demo: http://ngx-otp-input.vercel.app
- Angular
>=17.2.0 @angular/forms(required peer dependency)
npm install ngx-otp-inputimport { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { NgxOtpInputComponent, OtpStatus } from 'ngx-otp-input';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule, NgxOtpInputComponent],
template: `
<form [formGroup]="form">
<ngx-otp-input
formControlName="otp"
[length]="6"
[status]="status"
(otpComplete)="verifyOtp($event)"
></ngx-otp-input>
</form>
`,
})
export class AppComponent {
status: OtpStatus = 'idle';
form = new FormGroup({
otp: new FormControl('', { nonNullable: true }),
});
verifyOtp(code: string): void {
// Verify the code, then set status to 'success' or 'error'
}
}| Input | Type | Default | Description |
|---|---|---|---|
length |
number |
6 |
Number of OTP characters / visible boxes. |
autoFocus |
boolean |
true |
Focuses the input after view init (unless disabled). |
autoBlur |
boolean |
true |
Blurs the input when the OTP becomes complete. |
mask |
boolean |
false |
Masks rendered characters (•) in the visual boxes. |
charPattern |
RegExp |
/^\d$/ |
Per-character allowlist used during typing/paste sanitization. |
inputMode |
string |
'numeric' |
Sets the native input's inputmode for mobile keyboard hints. |
ariaLabel |
string |
'One Time Password' |
Accessible label for the OTP group. |
status |
OtpStatus ('idle' | 'success' | 'error') |
'idle' |
Visual + screen reader status state. |
statusMessages |
OtpStatusMessages |
{ success: 'Code verified.', error: 'Invalid code.' } |
Custom screen reader status messages. |
| Output | Payload | Description |
|---|---|---|
otpChange |
OtpChangeEvent |
Fires whenever value changes ({ value, isComplete }). |
otpComplete |
string |
Fires when value reaches length. |
otpInvalid |
OtpInvalidEvent |
Fires when input is rejected (too-long or char-rejected). |
Exported types:
OtpStatusOtpStatusMessagesOtpChangeEventOtpInvalidEvent
NgxOtpInputComponent exposes:
reset(): void- clears the current value and moves the caret to index0.
Example:
import { Component, ViewChild } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { NgxOtpInputComponent } from 'ngx-otp-input';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule, NgxOtpInputComponent],
template: `
<form [formGroup]="form">
<ngx-otp-input
#otpInput
formControlName="otp"
></ngx-otp-input>
</form>
<button
type="button"
(click)="resetOtp()"
>
Reset
</button>
`,
})
export class AppComponent {
@ViewChild('otpInput') otpInput?: NgxOtpInputComponent;
form = new FormGroup({
otp: new FormControl('', { nonNullable: true }),
});
resetOtp(): void {
this.otpInput?.reset();
}
}The component renders a native input plus a visual boxed UI. These classes are available:
| Class | Description |
|---|---|
ngx-otp-input-root |
Root wrapper (role="group"). |
ngx-otp-input-native |
Hidden-but-accessible native input element. |
ngx-otp-input-form |
Visual boxes container. |
ngx-otp-input-box |
Individual box. |
ngx-otp-input-active |
Active box based on caret position. |
ngx-otp-input-disabled |
Disabled visual state. |
ngx-otp-input-filled |
Box has a character. |
ngx-otp-input-success |
Success status style. |
ngx-otp-input-failed |
Error status style (status === 'error'). |
ngx-otp-input-status |
Screen reader status message container. |
Override styles from a global stylesheet (for example, src/styles.css):
ngx-otp-input {
.ngx-otp-input-box {
border-radius: 10px;
font-weight: 600;
}
.ngx-otp-input-box.ngx-otp-input-active {
outline: 2px solid #4f46e5;
}
}- Uses one real input for robust keyboard, paste, and autofill behavior.
- Uses
autocomplete="one-time-code"to improve OTP autofill. - Announces status messages with an
aria-liveregion. - Marks invalid state via
aria-invalid. - Keeps visual boxes
aria-hiddento avoid duplicate announcements.
- Input is truncated to
length. - Characters not matching
charPatternare discarded. - Rejected input emits
otpInvalid. - Valid changes emit
otpChange; completion emitsotpComplete.
Version 2.x introduces a new CVA-first API:
optionsinput removed. Use explicit inputs like[length],[mask], and[charPattern].- direct
otpinput removed. Use Angular Forms (formControl,formControlName, orngModel). otpChangepayload changed fromstring[]to{ value: string; isComplete: boolean }.- status values changed to
'idle' | 'success' | 'error'.
- Library package readme:
projects/ngx-otp-input/README.md - Documentation archive:
docs/README.md - Legacy
v1.1.4docs:docs/v1.1.4/README.md - Changelog:
CHANGELOG.md - Contributing guide:
CONTRIBUTING.md - Code of conduct:
CODE_OF_CONDUCT.md
MIT. See LICENSE.