A customizable standalone Angular OTP input component with RTL/LTR support, masking, auto-submit, and reactive forms integration.
npm install ngx-otp-inputsbash
npm install ngx-otp-inputs
`
> Requires Angular 14+ with Standalone Component support.
---
⚡ Usage
$3
`ts
import { Component } from "@angular/core";
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from "@angular/forms";
import { NgxOtpInputsComponent } from "ngx-otp-inputs";
@Component({
standalone: true,
imports: [ReactiveFormsModule, NgxOtpInputsComponent],
template:
,
})
export class ExampleReactive {
status: "success" | "failed" | null = null;
form = new FormGroup({
otp: new FormControl("", [Validators.required]),
});
submit() {
const code = this.form.value.otp ?? "";
// TODO: verify code via API, then set visual status:
this.status = code ? "success" : "failed";
console.log(code);
}
}
`
---
$3
`ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
import { NgxOtpInputsComponent } from "ngx-otp-inputs";
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
NgxOtpInputsComponent, // ✅ Import directly here
],
bootstrap: [AppComponent],
})
export class AppModule {}
`
`html
OTP Value: {{ otp }}
`
`ts
// app.component.ts
import { Component } from "@angular/core";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
})
export class AppComponent {
otp = "";
onCompleted(code: string) {
console.log("Completed:", code);
}
}
`
---
🎯 Inputs & Outputs
$3
| Input | Type | Default | Description |
| -------------- | ------------------------------------------------------ | --------------- | ----------------------------------------------- |
| length | number | 4 | Number of OTP digits |
| direction | 'ltr' \| 'rtl' | 'ltr' | Input direction |
| disabled | boolean | false | Disables all OTP inputs |
| readonly | boolean | false | Makes inputs read-only |
| maskInput | boolean | false | Masks each character as a password |
| autoSubmit | boolean | false | Emits completed automatically |
| autoBlur | boolean | false | Blurs last input on completion |
| autoFocus | boolean | true | Automatically focuses the first input |
| inputType | 'number' \| 'text' \| 'alphanumeric' | 'number' | Input filtering pattern |
| inputMode | 'numeric' \| 'text' \| 'decimal' \| 'tel' \| 'email' | 'numeric' | Helps mobile keyboards |
| inputClass | string | 'otp-input' | Custom CSS class for each OTP input |
| wrapperClass | string | 'otp-wrapper' | Custom CSS class for the wrapper |
| ariaLabels | string[] | [] | Accessibility labels for each input |
| status | 'success' \| 'failed' \| null | null | Visual state (adds success/error border colors) |
$3
| Output | Type | Description |
| ----------- | ---------------------- | -------------------------------- |
| completed | EventEmitter | Emits OTP when all fields filled |
| changed | EventEmitter | Emits OTP on every input change |
---
🛠 Public Methods
$3
Clears all input boxes.
Example:
`ts
import { Component, ViewChild } from "@angular/core";
import { NgxOtpInputsComponent } from "ngx-otp-inputs";
@Component({
standalone: true,
imports: [NgxOtpInputsComponent],
template:
,
})
export class ResetExample {
@ViewChild("otp") otp!: NgxOtpInputsComponent;
resetOtp() {
this.otp.reset();
}
}
`
---
🎨 Styling
Customize with CSS variables:
`css
:root {
--ngx-otp-width: 48px;
--ngx-otp-height: 56px;
--ngx-otp-border-radius: 8px;
--ngx-otp-border-color: #ccc;
--ngx-otp-focus-border-color: #1976d2;
--ngx-otp-font-size: 18px;
--ngx-otp-bg: #fff;
--ngx-otp-text-color: #000;
--ngx-otp-gap: 8px;
}
`
Status helpers (applied automatically when [status] is set):
`css
.otp-success .otp-input {
border-color: #10b981 !important;
} / success /
.otp-failed .otp-input {
border-color: #ef4444 !important;
} / failed /
``