npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ngxpert/input-otp

v1.0.3

Published

One-time password input component for Angular.

Readme

The only accessible & unstyled & full featured Input OTP component for Angular

All Contributors

OTP Input for Angular 🔐 by @shhdharmen

Usage

ng add @ngxpert/input-otp

Then import the component.

import { InputOTPComponent } from '@ngxpert/input-otp';
@Component({
  selector: 'app-my-component',
  template: `
    <input-otp [maxLength]="6" [(ngModel)]="otpValue" #otpInput>
      <div style="display: flex;">
        @for (slot of otpInput.slots(); track $index) {
          <div>{{ slot.char }}</div>
        }
      </div>
    </input-otp>
  `,
  imports: [InputOTPComponent, FormsModule],
})
export class MyComponent {
  otpValue = '';
}

Features

  • ✅ Works with Template-Driven Forms and Reactive Forms out of the box.
  • ✅ Supports copy-paste-cut
  • ✅ Supports all keybindings

Default example

The example below uses tailwindcss tailwind-merge clsx. You can see it online here, code available here.

main.component

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: `
    <input-otp
      [maxLength]="6"
      containerClass="group flex items-center has-[:disabled]:opacity-30"
      [(ngModel)]="otpValue"
      #otp="inputOtp"
    >
      <div class="flex">
        @for (
          slot of otp.slots().slice(0, 3);
          track $index;
          let first = $first;
          let last = $last
        ) {
          <app-slot
            [isActive]="slot.isActive"
            [char]="slot.char"
            [placeholderChar]="slot.placeholderChar"
            [hasFakeCaret]="slot.hasFakeCaret"
            [first]="first"
            [last]="last"
          />
        }
      </div>
      <app-fake-dash />
      <div class="flex">
        @for (
          slot of otp.slots().slice(3, 6);
          track $index + 3;
          let last = $last;
          let first = $first
        ) {
          <app-slot
            [isActive]="slot.isActive"
            [char]="slot.char"
            [placeholderChar]="slot.placeholderChar"
            [hasFakeCaret]="slot.hasFakeCaret"
            [first]="first"
            [last]="last"
          />
        }
      </div>
    </input-otp>
  `,
  imports: [FormsModule, InputOTPComponent, SlotComponent, FakeDashComponent],
})
export class ExamplesMainComponent {
  otpValue = '';
}

slot.component

import { Component, Input } from '@angular/core';
import { FakeCaretComponent } from './fake-components';
import { cn } from './utils';

@Component({
  selector: 'app-slot',
  template: `
    <div
      [class]="
        cn(
          'relative w-10 h-14 text-[2rem]',
          'flex items-center justify-center',
          'transition-all duration-300',
          'border-y border-r',
          'group-hover:border-accent-foreground/20 group-focus-within:border-accent-foreground/20',
          'outline outline-0 outline-accent-foreground/20',
          { 'outline-4 outline-accent-foreground': isActive },
          { 'border-l rounded-l-md': first },
          { 'rounded-r-md': last }
        )
      "
    >
      @if (char) {
        <div>{{ char }}</div>
      } @else {
        {{ ' ' }}
      }
      @if (hasFakeCaret) {
        <app-fake-caret />
      }
    </div>
  `,
  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;
}

fake-components

import { Component } from '@angular/core';

@Component({
  selector: 'app-fake-dash',
  template: `
    <div class="flex w-10 justify-center items-center">
      <div class="w-3 h-1 rounded-full bg-black/75"></div>
    </div>
  `,
})
export class FakeDashComponent {}

@Component({
  selector: 'app-fake-caret',
  template: `
    <div
      class="absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink"
    >
      <div class="w-[2px] h-8 bg-black/75"></div>
    </div>
  `,
})
export class FakeCaretComponent {}

utils

import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

import type { ClassValue } from 'clsx';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

styles

@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

<input-otp>

The root container. Define settings for the input via inputs. Then, use the inputOtp.slots() property to create the slots.

Inputs and outputs

export interface InputOTPInputsOutputs {
  // The number of slots
  maxLength: InputSignal<number>;

  // 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: <input-otp [pattern]="REGEXP_ONLY_DIGITS">
  pattern?: InputSignal<string | RegExp | undefined>;

  // 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<string | undefined>;

  // Virtual keyboard appearance on mobile
  // Default: 'numeric'
  inputMode?: InputSignal<'numeric' | 'text'>;

  // The autocomplete attribute for the input
  // Default: 'one-time-code'
  autoComplete?: InputSignal<string | undefined>;

  // The class name for the container
  containerClass?: InputSignal<string | undefined>;

  // Emits the complete value when the input is filled
  complete: OutputEmitterRef<string>;
}

Contributors ✨

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!