One-time password input component for Angular.
npm install @ngxpert/input-otp!NPM Version
!GitHub License


https://github.com/user-attachments/assets/697cdaf3-e4cd-4f7e-b7e6-f492c295bac4
``bash`
ng add @ngxpert/input-otp
Import the component.
`ts
import { InputOTPComponent } from '@ngxpert/input-otp';
@Component({
selector: 'app-my-component',
template:
@for (slot of otpInput.slots(); track $index) {
{{ slot.char }}
}
,`
imports: [InputOTPComponent, FormsModule],
})
export class MyComponent {
otpValue = '';
}
- ✅ Works with Template-Driven Forms and Reactive Forms out of the box.
- ✅ Supports copy-paste-cut
- ✅ Supports all keybindings
The example below uses tailwindcss tailwind-merge clsx. You can see it online here, code available here.
`ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { InputOTPComponent } from '@ngxpert/input-otp';
import { SlotComponent } from './slot.component';
import { FakeDashComponent } from './fake-components';
@Component({
selector: 'app-examples-main',
template:
containerClass="group flex items-center has-[:disabled]:opacity-30"
[(ngModel)]="otpValue"
#otp="inputOtp"
>
,
imports: [FormsModule, InputOTPComponent, SlotComponent, FakeDashComponent],
})
export class ExamplesMainComponent {
otpValue = '';
}`$3
`ts
import { Component, Input } from '@angular/core';
import { FakeCaretComponent } from './fake-components';
import { cn } from './utils';@Component({
selector: 'app-slot',
template:
,
imports: [FakeCaretComponent],
})
export class SlotComponent {
@Input() isActive = false;
@Input() char: string | null = null;
@Input() placeholderChar: string | null = null;
@Input() hasFakeCaret = false;
@Input() first = false;
@Input() last = false;
cn = cn;
}`$3
`ts
import { Component } from '@angular/core';@Component({
selector: 'app-fake-dash',
template:
,
})
export class FakeDashComponent {}@Component({
selector: 'app-fake-caret',
template:
,
})
export class FakeCaretComponent {}`$3
`ts
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';import type { ClassValue } from 'clsx';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
`$3
`css
@import "tailwindcss";@theme {
--animate-caret-blink: caret-blink 1.2s ease-out infinite;
@keyframes caret-blink {
0%,
70%,
100% {
opacity: 1;
}
20%,
50% {
opacity: 0;
}
}
}
`How it works
There's currently no native OTP/2FA/MFA input in HTML, which means people are either going with
1. a simple input design or
2. custom designs like this one.
This library works by rendering an invisible input as a sibling of the slots, contained by a
relatively positioned parent (the container root called _input-otp_).API Reference
$3
The root container. Define settings for the input via inputs. Then, use the
inputOtp.slots() property to create the slots.#### Inputs and outputs
`ts
export interface InputOTPInputsOutputs {
// The number of slots
maxLength: InputSignal; // Pro tip: input-otp export some patterns by default such as REGEXP_ONLY_DIGITS which you can import from the same library path
// Example: import { REGEXP_ONLY_DIGITS } from '@ngxpert/input-otp';
// Then use it as:
pattern?: InputSignal;
// While rendering the input slot, you can access both the char and the placeholder, if there's one and it's active.
// If you expect input to be of 6 characters, provide 6 characters in the placeholder.
placeholder?: InputSignal;
// Virtual keyboard appearance on mobile
// Default: 'numeric'
inputMode?: InputSignal<'numeric' | 'text'>;
// The autocomplete attribute for the input
// Default: 'one-time-code'
autoComplete?: InputSignal;
// The class name for the container
containerClass?: InputSignal;
// Emits the complete value when the input is filled
complete: OutputEmitterRef;
}
``Thanks goes to these wonderful people (emoji key):
Dharmen Shah ️️️️♿️ 💬 🐛 💻 🖋 📖 💡 🚧 📆 👀 ⚠️ | ||||||
Add your contributions | ||||||
This project follows the all-contributors specification. Contributions of any kind welcome!