Input OTP
Segmented one-time-password input built on the `input-otp` library, with paste-to-fill, autocomplete, and inter-slot focus management.
Overview
InputOTP renders a row of single-character slots that behave as a single underlying <input>. Built on top of input-otp, it handles inter-slot caret movement, paste-to-fill, and the browser's autocomplete="one-time-code" so SMS / email codes auto-populate on supported platforms.
Use it for 2FA, email verification, SMS confirmation - any ephemeral code the user types or pastes. Do not use it for passwords or persistent PINs; those need a regular Input with type="password".
Preview
Installation
bash npx gremorie@latest add rx-input-otp bash pnpm dlx gremorie@latest add rx-input-otp bash yarn dlx gremorie@latest add rx-input-otp bash bunx --bun gremorie@latest add rx-input-otp Usage
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from "@gremorie/rx-forms";
export function Example() {
return (
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
);
}Angular edition planned for Phase 5h. Star the repo to track progress.
API
<InputOTP>
| Prop | Type | Default | Description |
|---|---|---|---|
maxLength | number | - | Total number of slots. Required. |
value | string | - | Controlled value. |
onChange | (value: string) => void | - | Fires on every keystroke and paste. |
pattern | RegExp | string | - | Restrict allowed characters (e.g. ^[0-9]+$ for digits only). |
containerClassName | string | - | Classes applied to the wrapper row, not the underlying input. |
className | string | - | Classes applied to the underlying input (kept visually hidden). |
Extends all React.ComponentProps<typeof OTPInput> from input-otp. Renders with autocomplete="one-time-code" by default so the browser offers to autofill SMS codes on supported devices.
<InputOTPGroup>
Visual cluster of InputOTPSlots. Adds flex items-center and lets you split codes into chunks (e.g. 3 + 3) by rendering multiple groups separated by <InputOTPSeparator />.
Extends all React.ComponentProps<"div">.
<InputOTPSlot>
| Prop | Type | Default | Description |
|---|---|---|---|
index | number | - | Position in the underlying value (0-based). Required. |
Reads the active char, focus state, and fake caret position from OTPInputContext and renders the corresponding visual.
<InputOTPSeparator>
Decorative <div role="separator"> rendering a minus icon. Use between groups.
Composition
<InputOTP>is the controlled root - ownsvalue,maxLength, and the underlying hidden input.<InputOTPGroup>is a visual cluster. You can render multiple perInputOTPand separate them with<InputOTPSeparator />.<InputOTPSlot index={n} />is one character cell. Slots read their state from the sharedOTPInputContextbased on their index.
Variations
Six-digit code (single group)
The most common pattern for 2FA. maxLength={6} and six slots inside one group.
<InputOTP maxLength={6}>
<InputOTPGroup>
{Array.from({ length: 6 }).map((_, i) => (
<InputOTPSlot key={i} index={i} />
))}
</InputOTPGroup>
</InputOTP>Four-digit PIN
Short PIN code for confirmations. Reduce maxLength and the slot count together.
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>Grouped 3 + 3 with separator
Split a six-digit code with a separator for legibility (mirrors how codes are usually presented in emails: "123 456").
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>Numeric-only pattern
Restrict input to digits via the pattern prop. Prevents users from typing letters at all.
<InputOTP maxLength={6} pattern="^[0-9]+$">
<InputOTPGroup>
{Array.from({ length: 6 }).map((_, i) => (
<InputOTPSlot key={i} index={i} />
))}
</InputOTPGroup>
</InputOTP>Accessibility
- Single accessible input: behind the scenes,
input-otprenders one<input>element that owns the value, type, name, and label association. Screen readers see one field, not six. - Autocomplete:
autocomplete="one-time-code"lets iOS, Android, and modern browsers offer SMS auto-fill. - Keyboard navigation: typing moves the caret forward;
Backspaceclears and moves back; arrow keys move between slots; selection-to-paste spreads characters across slots. - Paste-to-fill: pasting a full code into any slot distributes characters across all slots in order.
- Active slot: the focused slot renders a blinking caret (
animate-caret-blink) and a focus ring, so users always see where the next keystroke lands. - Invalid state:
aria-invalid="true"on the root switches every slot's border to the destructive token.