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

@rxova/react-otp-input

v1.0.5

Published

A headless, accessible one-time-code input for React.

Readme

A headless, accessible one-time-code input for React. One real input underneath, real slots on top — including the tap-to-edit any slot that the incumbent can't do.

npm install @rxova/react-otp-input

📖 Documentation & live examples → — guides, form recipes, theming, WebOTP, and migration guides from other OTP libraries.

  • One real <input> — paste, SMS autofill, IME, undo, native <form> submission and screen-reader semantics all come from the platform, not from hand-rolled JavaScript
  • Tap any slot to edit it — the input's characters sit at their true slot pitch, so a click or tap lands the caret where you touched, which a collapsed single-input field physically can't do
  • Rapid typing stays ordered — if keyboard input begins before a click's caret placement has settled, the keyboard wins; a delayed pointer frame never moves the caret midway through the code
  • WebOTP — programmatic SMS retrieval (useWebOTP) that no other OTP library ships
  • Headless — zero runtime dependencies, no stylesheet to import, ~4.3 kB brotli
  • Form-ready — string onChange, native name, and first-class React Hook Form / Formik / React Final Form / TanStack Form recipes
  • Correct on the seams — formatted paste, Chrome auto-translate, IME, RTL, alphanumeric, SSR/RSC

Basic usage

import { useState } from "react";
import { OtpInput } from "@rxova/react-otp-input";

function Verify() {
  const [code, setCode] = useState("");
  return (
    <OtpInput
      length={6}
      value={code}
      onChange={setCode}
      onComplete={submit}
      label="One-time code"
    />
  );
}

| default | grouped 123–456 | alphanumeric | | :----------------------------------------------------------------------------------: | :---------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------: | | | | | | masked | invalid | render prop | | | | |

Four graduated tiers

One primitive, four levels of control. Simple stays a one-liner; complex stays possible without forking.

Tier 1 — the declarative default:

<OtpInput length={6} value={code} onChange={setCode} label="One-time code" />

Tier 2 — compound composition, for grouping and separators:

<OtpInput length={6} value={code} onChange={setCode} label="One-time code">
  <OtpGroup>
    <OtpSlot index={0} />
    <OtpSlot index={1} />
    <OtpSlot index={2} />
  </OtpGroup>
  <OtpSeparator>–</OtpSeparator>
  <OtpGroup>
    <OtpSlot index={3} />
    <OtpSlot index={4} />
    <OtpSlot index={5} />
  </OtpGroup>
</OtpInput>

Tier 3 — a render prop, when you want your own slot component:

<OtpInput
  length={6}
  value={code}
  onChange={setCode}
  render={({ slots }) => slots.map((s) => <MySlot key={s.index} {...s} />)}
/>

Tier 4 — the headless hook, to own the markup entirely:

const otp = useOtpInput({ length: 6, value: code, onChange: setCode });

Forms

onChange emits the sanitized string, and the underlying input posts natively under name.

With a plain <form>, the single input is the field — just pass name:

<OtpInput name="code" length={6} label="Code" />

With React Hook Form, Formik or React Final Form, bind value and onChange through a controlled adapter:

<Controller
  name="code"
  control={control}
  render={({ field }) => (
    <OtpInput
      length={6}
      label="Code"
      value={field.value}
      onChange={field.onChange}
      onBlur={field.onBlur}
      name={field.name}
      inputRef={field.ref}
    />
  )}
/>

Full recipes for React Hook Form, Formik, React Final Form, TanStack Form and native forms are in the form-library guide.

Styling

No stylesheet to import. Layout-critical CSS is inlined; everything visual is a custom property or a data-* hook.

| Property | Default | Applies to | | --------------------------------------- | ------------------------- | ------------------------------ | | --rx-otp-slot-size | 2.5rem | Slot width and height | | --rx-otp-gap | 0.5rem | Space between slots | | --rx-otp-font / -font-size | inherited / 1.125rem | Slot and separator typography | | --rx-otp-color / -bg | inherited / transparent | Slot foreground and background | | --rx-otp-border | 1px solid #d4d4d8 | Slot border | | --rx-otp-radius | 0.5rem | Slot corners | | --rx-otp-active-ring | 2px solid Highlight | Ring on the active slot | | --rx-otp-caret-color / -caret-width | currentColor / 2px | The painted caret | | --rx-otp-placeholder-color | inherited, dimmed | Per-slot placeholder | | --rx-otp-separator-color | inherited | <OtpSeparator> | | --rx-otp-transition | a short ease | Slot state changes |

data-* attributes

These are public API, covered by semver.

| Attribute | On | Meaning | | -------------------------------------------------- | --------- | ------------------------------- | | data-rx-otp-root | wrapper | Always present | | data-rx-otp-input | <input> | The single real control | | data-rx-otp-group | group | <OtpGroup> | | data-rx-otp-slot | slot | One painted slot | | data-rx-otp-separator | separator | <OtpSeparator> | | data-rx-otp-caret | caret | The painted caret | | data-state | slot | filled, active or empty | | data-filled / data-active | slot | Convenience flags for the above | | data-disabled / data-readonly / data-invalid | wrapper | Mirrors the props |

Keyboard

| Key | Effect | | --------------------- | ------------------------------------------------------- | | any allowed character | Fills the active slot and advances | | Backspace | Clears the slot, or steps back when it is already empty | | ← / → | Move between slots | | Home / End | Jump to the first or last slot | | Ctrl/Cmd + V | Paste and distribute, after pasteTransform | | Tab | Leave the field — it is one tab stop, not one per slot |

Accessibility

One native text <input> — a screen reader announces a single "one-time code, edit text", not "1 of 6" repeated. Slots are aria-hidden decoration. label / aria-label name the field; invalid + aria-describedby wire error text. Verified with axe in CI.

iOS note: the default slotInteraction="spatial" auto-degrades to "crush" on iOS, which cannot fully hide the native ::selection. Tap-to-edit stays keyboard-plus-caret there; it is full spatial everywhere else. Force either mode explicitly with the slotInteraction prop.

UI-library recipes

The docs contain maintained examples for shadcn/ui, Radix Themes, Material UI, Chakra UI, Mantine and Ant Design. Copy a ready-labelled wrapper with:

npx shadcn@latest add https://rxova.dev/packages/react-inputs/r/otp-field.json

Open the recipes.

Part of rxova

Part of the rxova headless React inputs suite — install the whole set from @rxova/react-inputs, or read the full generated API reference for this package.

Migrating? npx @rxova/codemod input-otp-to-otp --dry ./src handles most of the move from input-otp; the migration guide covers that and react-otp-input.

License

MIT © rxova