Examples

A gallery of finished fields you can copy: Stripe-style, segmented, underlined, masked, and more.

Every example on this page renders the same component with the same state. Only the markup changes. Flip to the Code tab on any of them — that source is read straight off disk, so it is exactly what is running above it.

A complete verification flow#

The one to copy if you are building the real thing: label, hint, auto-submit on completion, a pending state, an error that clears itself, focus recovery after a failure, and a resend cooldown. Type 424242 to succeed.

Check your phone

We sent a 6-digit code to •••• 4417.

Layouts#

Shared border#

The default look: one continuous box, divided.

Two groups with a dash#

Stripe's arrangement, and the reason many people recognise this pattern. slots is an array, so this is slice and a decorative divider.

Separated boxes#

Four rounded cells with gaps — the PIN shape.

Underlined#

No boxes at all: a rule under each character that thickens when active.

Keycaps#

Tactile cells with the character dropping in as it lands. The animation is keyed on slot.char and sits behind motion-safe:.

Full width#

Slots that flex instead of overflowing. Worth doing — six fixed-width slots will break a 320px viewport at 200% zoom.

// Slots that shrink instead of overflowing a narrow viewport.
<OTPInput
  maxLength={6}
  containerClassName="group flex w-full max-w-xs items-center"
  render={({ slots }) => (
    <div className="flex w-full">
      {slots.map((slot, idx) => (
        <Slot key={idx} {...slot} className="h-12 w-full min-w-0 flex-1 text-base" />
      ))}
    </div>
  )}
/>

Behaviour#

Placeholder#

0
0
0
0
0
0

Masked, with a reveal toggle#

Masking is a rendering decision — the value is untouched, so revealing it is one boolean.

Controlled#

value: ""

Auto-submit on completion#

Type six characters — no submit button needed.

Invalid state#

Disabled and read-only#

0
4
2
3
1
4
1
5
9

Alphanumeric#

Remember inputMode="text", or mobile users get a keypad with no letters on it.

Autofocus#

// Every input attribute is forwarded, autoFocus included.
<OTPInput autoFocus maxLength={6} />
 
// Prefer this over autoFocus when the field isn't the only thing on screen —
// autoFocus scrolls the page to it on load, which can be disorienting.
const ref = React.useRef<HTMLInputElement>(null)
React.useEffect(() => {
  if (userJustRequestedACode) ref.current?.focus()
}, [userJustRequestedACode])

Composition#

Labelled field#

A real <label>, a real hint, and no ARIA gymnastics — see Accessibility.

Enter the 6-character code we sent to your phone.

Named parts instead of a render prop#

Right-to-left#

أدخل الرمز المكون من ٦ أرقام

Text alignment#

Not a typography prop — it moves the invisible text, and with it the native caret and the iOS selection bubble. Why it matters.

1
2
3
4