mwui
GitHub

OTP Input

A one-time-code input with per-character slots and paste support.

Six digits, grouped

Waiting for a code.

Alphanumeric, masked

Installation

Usage

Verification codes deserve one box per character, and every implementation that renders one real <input> per box breaks something: paste fills only the first slot, iOS SMS autofill does not fire, backspace across boundaries gets weird, and the whole thing is unusable to a screen reader.

This one is a single input — transparent, stretched across the field — with the boxes drawn behind it. The platform keeps doing its job.

import { OtpInput } from "@/components/ui/otp-input";

export function VerifyForm() {
  return <OtpInput length={6} onComplete={(code) => verify(code)} />;
}

onComplete fires as soon as the last slot fills, which is the moment to submit — asking someone to press a button after typing the sixth digit is a step nobody needs.

What you get for free

  • Paste puts the whole code in, from anywhere in the field.
  • SMS autofill works: autocomplete="one-time-code" on a real input is what iOS and Android look for. Splitting into several inputs is exactly what disables it.
  • Selection behaves — shift-arrow, select-all, drag — and the highlighted slots follow it.
  • Password managers and IME see one ordinary text field.

Format

<OtpInput length={6} groupSize={3} />
<OtpInput length={4} pattern="alphanumeric" />
<OtpInput length={6} mask />

pattern accepts "numeric" (the default), "alphanumeric", or your own single-character RegExp — characters that fail it are dropped as they arrive, including from a paste, so a copied code with spaces or dashes still lands correctly.

groupSize inserts a separator every N slots. mask renders dots, for codes that act as secrets rather than transcriptions.

Props

PropDescription
lengthNumber of slots. Defaults to 6.
value / defaultValue / onValueChangeControlled or uncontrolled string.
onCompleteCalled with the full code once the last slot fills.
pattern"numeric", "alphanumeric", or a single-character RegExp.
groupSizeInsert separator every N slots.
separatorNode rendered between groups. Defaults to "-".
maskRender filled slots as dots.
namePosts the code in a plain HTML form.
aria-labelLabels the field. Defaults to "One-time code".

Accessibility

The slots are aria-hidden decoration; the input underneath carries the label and the value, so assistive technology reads a single labelled text field rather than announcing six unlabelled boxes. The blinking caret is drawn on the active empty slot, since the real caret is hidden.