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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shubhh6003/react-native-otp-input

v0.1.0

Published

Production-quality, accessible, pure-JS OTP and PIN input component for React Native and Expo with SMS autofill, paste support, and full customization.

Readme

@shubhh6003/react-native-otp-input

A polished, accessible, zero-dependency, pure-JS OTP and PIN input component for React Native and Expo with SMS autofill, paste support, and full customization.

npm version npm downloads license CI Status TypeScript Platform


Demo

| iOS | Android | | :---: | :---: | | iOS Demo GIF | Android Demo GIF |

(Replace with recorded GIFs in your GitHub repository)


Features

  • ⚡ Zero Native Code: Pure JavaScript/TypeScript. Works seamlessly across iOS, Android, and Expo (both managed and bare workflows).
  • 📦 Zero Runtime Dependencies: Only requires react and react-native as peer dependencies.
  • 📱 Native SMS Autofill: Supports iOS One-Time Code (textContentType="oneTimeCode") and Android SMS OTP (autoComplete="sms-otp").
  • 📋 Seamless Paste Support: Paste formatted codes (e.g. 123 456, 12-34-56, or 123456) directly into the input. Automatically strips delimiters and truncates overflow.
  • ♿ Accessible by Design: Decorative visual boxes hidden from screen readers, while the hidden TextInput provides full accessibility hints and announcements.
  • 🔒 PIN & Secure Mode: Mask characters with custom mask symbols (•, ●, *) and optional alphanumeric support.
  • 🔄 Controlled & Uncontrolled: Effortlessly switch between controlled value or uncontrolled defaultValue.
  • ⏱️ Countdown Hook Included: Comes with useOtpTimer for self-cleaning "Resend code in 30s" timers.
  • 🎨 Fully Customizable: Flexible style props for containers, active boxes, filled boxes, error states, and text typography.

Installation

# npm
npm install @shubhh6003/react-native-otp-input

# yarn
yarn add @shubhh6003/react-native-otp-input

# expo
npx expo install @shubhh6003/react-native-otp-input

Quick Start (Controlled)

import React, { useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { OtpInput } from '@shubhh6003/react-native-otp-input';

export default function VerificationScreen() {
  const [code, setCode] = useState('');

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Enter Verification Code</Text>
      <OtpInput
        length={6}
        value={code}
        onChange={setCode}
        onComplete={completedCode => {
          console.log('OTP verified:', completedCode);
        }}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 20,
    fontWeight: '600',
    marginBottom: 20,
  },
});

Props Reference

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | length | number | 6 | Number of boxes (allowed 3 to 8). | | value | string | undefined | Controlled value. | | defaultValue | string | '' | Initial value for uncontrolled usage. | | onChange | (code: string) => void | undefined | Callback fired on every change. | | onComplete | (code: string) => void | undefined | Callback fired once when all boxes are filled. | | autoFocus | boolean | true | Auto-focus the input on mount. | | type | 'numeric' \| 'alphanumeric' | 'numeric' | Allowed characters; 'numeric' uses keyboardType="number-pad". | | secureTextEntry | boolean | false | Mask characters for PIN/password input. | | maskChar | string | '•' | Character displayed when secureTextEntry is true. | | placeholder | string | '' | Placeholder character shown in empty boxes (e.g. '-'). | | disabled | boolean | false | Disables user interaction and dims the boxes. | | error | boolean | false | Applies error styling to all boxes. | | errorMessage | string | undefined | Optional error message text displayed below the boxes. | | showCursor | boolean | true | Blinking caret shown in the active empty box. | | separatorAfter | number[] | [] | Inserts a separator after specified box positions (e.g. [3] makes 123 - 456). | | renderSeparator | (index?: number) => ReactNode | undefined | Custom separator renderer function. | | autoSubmit | boolean | true | Calls onComplete automatically when code is full. | | blurOnComplete | boolean | false | Automatically dismisses the keyboard when complete. | | clearOnError | boolean | false | Clears the input value when error becomes true. | | containerStyle | StyleProp<ViewStyle> | undefined | Custom style for the root container. | | boxStyle | StyleProp<ViewStyle> | undefined | Custom style for each individual box. | | activeBoxStyle | StyleProp<ViewStyle> | undefined | Custom style for the active (focused) box. | | filledBoxStyle | StyleProp<ViewStyle> | undefined | Custom style for boxes containing a character. | | errorBoxStyle | StyleProp<ViewStyle> | undefined | Custom style for boxes in error state. | | textStyle | StyleProp<TextStyle> | undefined | Custom typography style for the character text. | | cursorStyle | StyleProp<ViewStyle> | undefined | Custom style for the blinking cursor caret. | | errorTextStyle | StyleProp<TextStyle> | undefined | Custom style for the error message text. | | testID | string | undefined | Base testID for automated testing. | | accessibilityLabel | string | 'One time password' | Accessibility label for screen readers. | | onFocus | () => void | undefined | Callback fired when the input gains focus. | | onBlur | () => void | undefined | Callback fired when the input loses focus. |


Imperative Ref API

You can access imperative methods using a standard React ref:

import React, { useRef } from 'react';
import { Button } from 'react-native';
import { OtpInput, type OtpInputRef } from '@shubhh6003/react-native-otp-input';

export function OtpControl() {
  const otpRef = useRef<OtpInputRef>(null);

  return (
    <>
      <OtpInput ref={otpRef} length={6} />
      <Button title="Focus" onPress={() => otpRef.current?.focus()} />
      <Button title="Clear" onPress={() => otpRef.current?.clear()} />
      <Button title="Fill 654321" onPress={() => otpRef.current?.setValue('654321')} />
      <Button title="Log Value" onPress={() => console.log(otpRef.current?.getValue())} />
    </>
  );
}

Ref Methods

export type OtpInputRef = {
  focus: () => void;
  blur: () => void;
  clear: () => void;
  setValue: (code: string) => void;
  getValue: () => string;
};

SMS Autofill Guide

This package utilizes the single hidden TextInput architecture, ensuring that native OS autofill integrates seamlessly without native bridges.

iOS One-Time Code Autofill

  • How it works: On iOS 12+, when an incoming SMS contains an authentication code, iOS displays a QuickType suggestion bar above the software keyboard with the code.
  • TextInput props applied: textContentType="oneTimeCode"
  • Requirements:
    • The incoming SMS should contain phrases such as "verification code" or "code" alongside the digits.
    • The QuickType keyboard must be enabled in iOS Settings (Settings > General > Keyboards > Predictive).

Android Google Play Services Autofill

  • How it works: On Android 8.0+, Google Play Services provides SMS OTP autofill suggestions above the keyboard.
  • TextInput props applied: autoComplete="sms-otp", importantForAutofill="yes".
  • Note on Secure Mode: Enabling secureTextEntry={true} tells both iOS and Android that the field is sensitive/private, which may cause the OS to suppress SMS autofill suggestions. If you require autofill, keep secureTextEntry={false}.

Optional: Combining with SMS Retriever API

For zero-tap automatic SMS verification on Android, you can integrate with the Google SMS Retriever API using an external library such as react-native-otp-verify without adding native code to this component:

import React, { useEffect, useRef } from 'react';
import RNOtpVerify from 'react-native-otp-verify';
import { OtpInput, type OtpInputRef } from '@shubhh6003/react-native-otp-input';

export function SmsRetrieverExample() {
  const otpRef = useRef<OtpInputRef>(null);

  useEffect(() => {
    RNOtpVerify.getOtp()
      .then(() => RNOtpVerify.addListener(message => {
        // Extract 6-digit code using regex
        const match = /(\d{6})/.exec(message);
        if (match && match[1]) {
          otpRef.current?.setValue(match[1]);
        }
      }))
      .catch(console.error);

    return () => {
      RNOtpVerify.removeListener();
    };
  }, []);

  return <OtpInput ref={otpRef} length={6} />;
}

Examples

1. 4-Digit Secure PIN

<OtpInput
  length={4}
  secureTextEntry={true}
  maskChar="●"
  onComplete={pin => verifyPin(pin)}
/>

2. Alphanumeric Voucher / Promo Code

<OtpInput
  length={5}
  type="alphanumeric"
  placeholder="-"
  onChange={code => setVoucher(code)}
/>

3. Grouped with Separators (3 + 3)

<OtpInput
  length={6}
  separatorAfter={[3]}
  onChange={code => setCode(code)}
/>

4. Error State with Auto-Clear

<OtpInput
  length={6}
  error={isInvalid}
  errorMessage={isInvalid ? 'Invalid code. Please try again.' : undefined}
  clearOnError={true}
  onChange={() => setIsInvalid(false)}
/>

5. Resend Countdown Timer (useOtpTimer)

import React from 'react';
import { View, Button, Text } from 'react-native';
import { OtpInput, useOtpTimer } from '@shubhh6003/react-native-otp-input';

export function ResendExample() {
  const { secondsLeft, isRunning, restart } = useOtpTimer(30);

  return (
    <View>
      <OtpInput length={6} />
      <Button
        title={isRunning ? `Resend code in ${secondsLeft}s` : 'Resend Code'}
        disabled={isRunning}
        onPress={() => restart(30)}
      />
    </View>
  );
}

6. Custom Rounded / Circular Style

<OtpInput
  length={4}
  boxStyle={{
    width: 56,
    height: 56,
    borderRadius: 28,
    borderWidth: 2,
    borderColor: '#E2E8F0',
    backgroundColor: '#F8FAFC',
  }}
  activeBoxStyle={{
    borderColor: '#7C3AED',
    backgroundColor: '#F5F3FF',
  }}
  textStyle={{
    fontSize: 22,
    fontWeight: '700',
    color: '#6D28D9',
  }}
/>

7. Underline-Only Style

<OtpInput
  length={4}
  boxStyle={{
    width: 48,
    height: 56,
    borderRadius: 0,
    borderWidth: 0,
    borderBottomWidth: 3,
    borderBottomColor: '#CBD5E1',
    backgroundColor: 'transparent',
  }}
  activeBoxStyle={{
    borderBottomColor: '#059669',
  }}
  textStyle={{
    fontSize: 24,
    color: '#065F46',
  }}
/>

Accessibility

  • Decorative visual boxes: Wrapped with importantForAccessibility="no-hide-descendants" and accessibilityElementsHidden={true} to prevent screen readers from reading each individual box repeatedly.
  • Accessible hidden input: Contains descriptive accessibilityLabel (default: 'One time password') and accessibilityHint announcing the total number of characters required.
  • Live region announcements: Uses AccessibilityInfo.announceForAccessibility to alert users of errors and successful input completion.
  • Font scaling safeguard: Applies allowFontScaling={false} to individual box text to preserve the touch-target layout while maintaining readable dimensions.

Troubleshooting & FAQ

Q: Why does SMS autofill not show up on iOS?

  1. Ensure your test SMS message includes keywords like code or verification code (e.g. "Your verification code is 123456").
  2. Check that QuickType / Predictive text is enabled under iOS Settings > General > Keyboards > Predictive.
  3. If secureTextEntry={true} is enabled, iOS treats the input as confidential and disables autofill suggestions.

Q: Why does autoFocus sometimes fail on Android?

On certain Android devices and custom ROMs, focusing TextInput immediately during mount can be ignored if the activity window hasn't completed its layout transition. This library incorporates a small delayed requestAnimationFrame on mount to ensure reliable autofocus across Android versions.

Q: How does paste support work?

The component captures pasted strings via the hidden TextInput. Any non-allowed characters, formatting spaces, or hyphens (e.g. 123 456 or 12-34-56) are automatically sanitized and truncated to the specified length.

Q: Why is my keyboard closing automatically?

Ensure blurOnComplete is set to false (default) if you wish to keep the keyboard visible after the user completes entering all digits.


Contributing

Contributions are welcome! Please read CONTRIBUTING.md and CODE_OF_CONDUCT.md for details on submitting pull requests and running tests.


License

MIT © Shubh