Rxova
Skip to content

Migrating

@rxova/react-otp-input shares input-otp’s single-input architecture, so the mental model carries over — you gain spatial tap-to-edit, WebOTP, and a form-library-friendly onChange.

input-otp@rxova/react-otp-inputNotes
maxLengthlengthNumber of slots
value / onChangevalue / onChangeBoth emit a string
onCompleteonCompleteFires when the value fills
render={({ slots }) => …}render={({ slots }) => …} or the compound APISee below
containerClassNameclassNameOn the root
patternmode or patternmode="numeric" | "alphanumeric" | "alpha", or a custom pattern
pushPasswordManagerStrategy(not needed)No width hack; the field never shifts layout
slotInteraction"spatial" (default) enables tap-to-edit
webOTPOpt into WebOTP SMS retrieval
mask, placeholder, invalid, labelFirst-class

input-otp gives you a render prop and you hand-build the slot markup (the shadcn InputOTPSlot pattern). That still works here via render. But the grouped 123–456 layout everyone copies is now first-party:

// input-otp — render prop + your own slot component
<OTP maxLength={6} render={({ slots }) => (
<>
<div>{slots.slice(0, 3).map((s, i) => <Slot key={i} {...s} />)}</div>
<Separator />
<div>{slots.slice(3).map((s, i) => <Slot key={i} {...s} />)}</div>
</>
)} />
// @rxova/react-otp-input — the compound API, shipped
<OtpInput length={6} value={code} onChange={setCode} label="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>
  • Tap a middle slot to edit itinput-otp #32; it’s the default here.
  • No password-manager width hack — no pushPasswordManagerStrategy, no layout shift.
  • translate="no" — no crash on Chrome auto-translate.
  • WebOTPwebOTP / useWebOTP.
  • Forms<Controller> and native name both work; onChange is a string.

input-otp’s data-active / data-char slot attributes map onto [data-active], [data-filled], and [data-state]. There is no CSS to import — style with --otp-* tokens instead. See Styling.

react-otp-input renders N separate <input>s; @rxova/react-otp-input renders one. That single change fixes a cluster of the N-input problems for free — but it also means the API shifts from “configure N boxes” to “configure one field.”

react-otp-input@rxova/react-otp-inputNotes
numInputslengthNumber of slots
value / onChangevalue / onChangeonChange emits a string (react-otp-input does too)
renderInput(not needed)You don’t render N inputs; use the compound API or render
renderSeparator<OtpSeparator>Part of the compound API
inputType="number" | "tel" | "password"mode + maskmode="numeric"; mask for password style
shouldAutoFocusautoFocus
skipDefaultStyles(always headless)No styles to skip; use --otp-* tokens
containerStyle / inputStylestyle / className + tokens
onComplete, webOTP, slotInteraction, invalid, placeholderNew
// react-otp-input — N inputs, render each one
<OtpInput
value={code}
onChange={setCode}
numInputs={6}
renderSeparator={<span>-</span>}
renderInput={(props) => <input {...props} />}
/>
// @rxova/react-otp-input — one field
<OtpInput length={6} value={code} onChange={setCode} label="Code" />
  • SMS autofill fills the whole code, not just the first box.
  • No per-keystroke input-type churn — the mobile keyboard stays numeric (react-otp-input #327).
  • Backspace, arrows, Home/End, paste, and selection are native — no hand-rolled focus juggling.
  • One accessible field, one tab stop, one label — instead of six unlabelled boxes.
  • Native form submission via name; no hidden concat field.

Replace inputStyle / focusStyle with --otp-* tokens and the [data-active] / [data-filled] hooks. See Styling and Theming.