Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

ngx-otp-inputs

magdy-abas28MIT1.1.2TypeScript support: included

A customizable standalone Angular OTP input component with RTL/LTR support, masking, auto-submit, and reactive forms integration.

angular, angular-otp, angular-component, angular-17, otp, otp-input, otp-component, standalone, rtl, auth, authentication, verification, sms-code, 2fa, mfa, masking, auto-submit, reactive-forms

readme

🚀 ngx-otp-inputs

A customizable, standalone OTP input component for Angular 14+ with full RTL support, masking, paste handling, auto-submit, and keyboard navigation.

npm version Angular Demo License: MIT


📦 Installation

npm install ngx-otp-inputs

Requires Angular 14+ with Standalone Component support.


⚡ Usage

1) Standalone + Reactive Forms

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: `
    <form [formGroup]="form" (ngSubmit)="submit()">
      <lib-ngx-otp-inputs formControlName="otp" [length]="6" [inputType]="'number'" [inputMode]="'numeric'" [autoSubmit]="true" [direction]="'ltr'" [disabled]="false" [status]="status" (completed)="submit()"> </lib-ngx-otp-inputs>

      <button type="submit" [disabled]="form.invalid">Submit</button>
    </form>
  `,
})
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);
  }
}

2) Using with NgModule

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 {}
<!-- app.component.html -->
<lib-ngx-otp-inputs [(ngModel)]="otp" [length]="6" [autoSubmit]="true" (completed)="onCompleted($event)"> </lib-ngx-otp-inputs>

<p>OTP Value: {{ otp }}</p>
// 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

Inputs

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)

Outputs

Output Type Description
completed EventEmitter<string> Emits OTP when all fields filled
changed EventEmitter<string> Emits OTP on every input change

🛠 Public Methods

reset()

Clears all input boxes.

Example:

import { Component, ViewChild } from "@angular/core";
import { NgxOtpInputsComponent } from "ngx-otp-inputs";

@Component({
  standalone: true,
  imports: [NgxOtpInputsComponent],
  template: `
    <lib-ngx-otp-inputs #otp [length]="6"></lib-ngx-otp-inputs>
    <button (click)="resetOtp()">Reset</button>
  `,
})
export class ResetExample {
  @ViewChild("otp") otp!: NgxOtpInputsComponent;

  resetOtp() {
    this.otp.reset();
  }
}

🎨 Styling

Customize with CSS variables:

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

.otp-success .otp-input {
  border-color: #10b981 !important;
} /* success */
.otp-failed .otp-input {
  border-color: #ef4444 !important;
} /* failed  */

🌐 Live Demo

Try it here: https://ngx-otp-inputs-demo.vercel.app


🤝 Contributing

Contributions, issues, and feature requests are welcome!
Feel free to check the issues page.


📄 License

MIT License