Skip to content

Components · Forms

OTPPinInput

One-time code or PIN entry in separate boxes, backed by one accessible input.

@rookcom/monarch-reactComponent

Verify a code

Try 123456.

Usage examples

PIN and groups

Purpose

Enters a short numeric or alphanumeric code (an email or SMS one-time code, a step-up PIN) shown as one box per character, while remaining a single real input for autofill, paste, mobile keypads and screen readers.

Use when

  • Sign-in or step-up verification with a code sent to the user.
  • A short PIN where the box per character shows how many are needed.

Avoid when

  • Passwords or long secrets: use a password Input.
  • Codes of variable length: use Input.

Design principles

  • One field, many boxes: the boxes are a picture of one <input>, so the platform features for one-time codes just work.
  • Forgiving entry: spaces, dashes and other characters in a pasted or typed code are dropped.
  • Verification is the product’s: onComplete hands over the code, and the product shows the result through Field.

API reference

import { OTPPinInput } from '@rookcom/monarch-react';
ExportKindRenderingRoot class
OTPPinInputcomponentclient.mn-otp-pin-input

Renders .mn-otp-pin-input with aria-hidden slots and a transparent <input> laid over them. The ref and native input props go to the input; className goes to the wrapper.

Reads Field context through useFieldControl for its label, description, invalid and disabled.

Generated from the TypeScript declarations of packages/react/src/components/forms/otp-pin-input.

Properties

maxLength, minLength and pattern are set by the component from length and mode and cannot be passed.

OTPPinInput

A one-time code or PIN entry shown as separate boxes. It is one real `<input>` underneath, so autofill from SMS or email (`autocomplete="one-time-code"`), paste, the numeric keypad and screen readers all treat it as a single field. Give it a label with Field or `aria-label`.

PropTypeDescription
childrenReactNode
classNamestringClass for the outer wrapper (the real input is `.mn-otp-pin-input__input`).
defaultValuestringInitial code when uncontrolled.
disabledboolean
groupSizenumberDraw a gap after every N slots, e.g. 3 for "123 456". Visual only.
idstring
invalidbooleanMarks the code invalid (e.g. after a failed verification). Inside a Field, the Field's `invalid` is used.
lengthnumberNumber of characters. Default 6.
maskbooleanShow dots instead of the characters, for a PIN typed on a shared screen.
mode"numeric" | "alphanumeric"`numeric` (default) accepts digits and opens the number keypad; `alphanumeric` accepts A–Z and 0–9, upper-cased.
onComplete((value: string) => void)Called once each time the code reaches `length` characters (typed, pasted or autofilled).
onValueChange((value: string) => void)Called with the cleaned code on every change.
size"md" | "lg"
valuestringThe code (controlled).

Variants

PropValuesDescription
length'number (default 6)'Number of boxes and characters.
mode'numeric''alphanumeric'numeric keeps digits and opens the number keypad; alphanumeric keeps A–Z and 0–9, upper-cased.
mask'boolean'Dots instead of characters; the input becomes type="password".
groupSize'number'A visual gap after every N boxes (e.g. 3 for "123 456").
size'md''lg'Box size. Default md.

States

StateExposed asBehaviour
Focuseddata-focusedThe next box to fill shows the focus ring and a blinking caret.
Filleddata-filled on a slotThe box shows its character or a dot.
Completedata-completeEvery box is filled; onComplete has fired.
Invaliddata-invalid, aria-invalid="true"Danger borders, from invalid or the Field.
Disableddata-disabled, disabledMuted boxes, for example while verifying.

Interaction behaviour

  • Typing fills the boxes in order; characters that do not fit the mode are dropped.
  • Pasting a full code replaces the value; pasting a fragment appends it. Spaces and dashes are removed.
  • Autofill from SMS or email (autocomplete="one-time-code") fills every box at once.
  • onComplete fires each time the code reaches length characters, whether typed, pasted or autofilled.
  • The caret always stays at the end, so editing is by Backspace; clicking any box focuses the field.
  • Controlled with value + onValueChange (set "" to reset after a failed attempt), or uncontrolled with defaultValue.

Keyboard support

KeysAction
CharactersFill the next box.
BackspaceClears the last filled box.
Ctrl/⌘ + VPastes a code.
TabMoves focus in and out as one field.

Accessibility

Role
textbox (one native input)
Accessible name
A FieldLabel or aria-label such as "6-digit code". Screen readers hear one field and its value, not six unnamed boxes.
  • The visual boxes are aria-hidden.
  • Errors and help come from Field and are linked with aria-describedby.
  • inputmode="numeric" and pattern="[0-9]*" open the number keypad on phones without blocking paste.
  • With mask, the input is type="password", so assistive technology does not read the PIN aloud.

Responsive behaviour

  • Boxes are 2.75rem wide (3.25rem at lg) and shrink evenly when the container is narrower, down to 1.5rem each, so even an 8-character grouped code fits a 320px screen. In a shrink-to-fit container (inline or width: max-content) give the field a width, or the boxes stay at that minimum.
  • The input font size is 16px, which keeps iOS from zooming on focus.

Theme support

  • Box borders, surfaces and the focus ring are semantic tokens, so light, dark and more-contrast themes apply.
  • The caret blink and box transitions stop under prefers-reduced-motion.

Do and don't

Do

  • Verify in onComplete and also offer a Confirm button for users who type slowly.
  • Disable the field (through Field) while the code is verified, and clear it after a wrong code.
  • Tell the user where the code was sent and how many characters it has.

Don't

  • Do not build one input per box; autofill and screen readers break.
  • Do not reveal whether a code was close; just say it was not right.
  • Input: Input for codes of variable length.
  • Field: Label, help and error wiring.
  • Button: The Confirm action next to it.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsField

Testing coverage

  • One labelled input with one-time-code, numeric keypad and pattern; slots are aria-hidden.
  • Typing fills slots, drops other characters, and onComplete fires once at full length.
  • Backspace deletes.
  • Pasting a full code (with spaces and dashes) replaces; a fragment appends.
  • Other lengths, alphanumeric mode with upper-casing, and groups.
  • Mask shows dots and uses a password input.
  • Controlled mode and reset by the parent.
  • Reads Field label, error, invalid and disabled.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery; Cross-browser axe: /components/otp-pin-input

Tests: packages/react/src/components/forms/otp-pin-input