A customizable, standalone OTP input component for Angular 14+ with full RTL support, masking, paste handling, autoβsubmit, and keyboard navigation.
π If you like this project, please consider giving it a β on GitHub to support the development!
- Features
- Installation
- Compatibility
- Quick Start
- API
- Styling & Theming
- Mobile & Accessibility
- Best Practices
- Live Demo
- Development
- License
- Contributing
- FAQ
- β RTL & LTR Support
- β ControlValueAccessor
- β Auto Submit
- β Auto Blur
- β Input Masking
- β Paste Handling
- β Keyboard Navigation
- β Accessibility Ready
- β Full Custom Styling
npm install ngx-otp-inputs
Requires Angular 14+ with Standalone Component support.
- Angular 14+
- Works with Reactive Forms & Template-driven Forms
- SSR-friendly
import { NgxOtpInputsComponent } from "ngx-otp-inputs";
@Component({
standalone: true,
imports: [NgxOtpInputsComponent],
template: `<lib-ngx-otp-inputs [length]="6" [maskInput]="false" [autoSubmit]="true" [direction]="'ltr'" (completed)="onCompleted($event)" (changed)="onChanged($event)" />`,
})
export class MyComponent {
onCompleted(code: string) {
console.log("Completed:", code);
}
onChanged(code: string) {
console.log("Changed:", code);
}
}
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" (completed)="onCompleted($event)"> </lib-ngx-otp-inputs>
<button type="submit" [disabled]="form.invalid">Submit</button>
<div *ngIf="form.invalid && form.touched" class="error">Enter 6 digits</div>
</form>
`,
})
export class ExampleReactive {
form = new FormGroup({
otp: new FormControl("", [Validators.required]),
});
onCompleted(code: string) {
console.log(code);
}
submit() {
console.log(this.form.value);
}
}
<lib-ngx-otp-inputs [(ngModel)]="otp" [length]="4" [direction]="'rtl'" (completed)="onCompleted($event)"> </lib-ngx-otp-inputs>
<p>Value: {{ otp }}</p>
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { AppComponent } from "./app.component";
import { NgxOtpInputsComponent } from "ngx-otp-inputs";
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule, ReactiveFormsModule, NgxOtpInputsComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
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) |
Output | Type | Description |
---|---|---|
completed |
EventEmitter<string> |
Emits OTP when all fields filled |
changed |
EventEmitter<string> |
Emits OTP on every input change |
@ViewChild(NgxOtpInputsComponent) otp!: NgxOtpInputsComponent;
// Clear all inputs
this.otp.reset();
You can fully style the component using CSS variables:
Variable | Default |
---|---|
--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 |
- Use
[inputMode]="'numeric'"
for numeric keyboard on mobile. - For SMS OTP auto-fill, the first input uses
autocomplete="one-time-code"
.
- For numeric OTPs, prefer
[inputType]="'number'"
. - Fix
length
and rely on form validation. - Prefer
inputMode="numeric"
overtype="number"
on mobile.
# Clone repository
git clone https://github.com/magdy-abas/ngx-otp-inputs
cd ngx-otp-inputs
# Run demo locally
cd demo
npm i
npm start
# Build the library
cd ../projects/ngx-otp-inputs
npm run build
MIT License β free for commercial and personal use.
PRs & issues are welcome!
Q: Why not use a single <input>
with a mask?
A: Multi-input UX gives clearer visual progress and works better with paste + mobile keyboards.
Q: Does it work with SSR?
A: Yes. The component avoids direct DOM globals, so itβs SSR-friendly.
Q: How do I programmatically set the value?
A: If youβre using Reactive Forms, patchValue({ otp: '123456' })
will distribute characters automatically.