Skip to content

Input OTP

A segmented input for short fixed-length codes (one-time passwords, 2FA, PINs).

Stablev0.4.2added in v0.1.0@garn/ui/input-otp
On this page

Default

A 6-digit code split 3 + 3.

Installation

garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.

terminal
npx garn-ui add input-otp
First time? Set up garn in your project
Registry
input-otp
Deps
input-otplucide-react
Registry deps
utils

Import

import { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from "@garn/ui/input-otp";

Anatomy

The parts this component is built from — compose them to assemble it.

InputOTPRequired[data-slot="input-otp"]

Root — owns the value; maxLength sets how many characters/slots.

InputOTPGroupRequired[data-slot="input-otp-group"]

Visually groups a run of slots.

InputOTPSlotRequired[data-slot="input-otp-slot"]

One character cell; addressed by its index.

InputOTPSeparator[data-slot="input-otp-separator"]

A divider between groups (e.g. 3 + 3).

Props

The component's public props and their types.

No component-specific props — this is a thin wrapper over its native element.

Styling

Target these data-slots and states, and remap these tokens, to restyle without forking the component.

[data-slot="input-otp"]

States

SelectorState
[data-slot=input-otp-slot] (active)active-slot
[aria-invalid=true]invalid
:disableddisabled
PropertyTokenTier
slotSize--garn-control-h-mdsemantic · air

When to use

Enter a short, fixed-length code — OTP, 2FA, email/phone verification, a PIN.

Reach for something else when

  • General or variable-length text (use input)
  • long secrets/passwords (use input type=password).

Overview

InputOTP collects a short, fixed-length code — a one-time passcode, 2FA token, email/phone verification, or PIN — as a row of single-character slots. InputOTP owns the value (maxLength sets the slot count), InputOTPGroup clusters slots, InputOTPSlot is one cell by index, and InputOTPSeparator splits a long code (3 + 3). For general or variable-length text use Input; for a secret password use a password Input.

Guidelines

Use it only for short, fixed-length codes. It's built for 4–8 known characters; anything variable-length or freeform belongs in a regular Input. Match maxLength to the real code and group long codes (e.g. 3 + 3) for readability.

Make entry effortless. Support paste-to-fill-all and auto-advance between slots, set the right inputMode/autoComplete (one-time-code) so mobile keyboards and SMS autofill work, and auto-submit when the last slot fills if that fits the flow.

Keep it accessible and recoverable. The field needs an accessible name, and the slots should read as one input to assistive tech, not N anonymous boxes. On a wrong code, show a clear error and let the user retry or request a new code rather than silently clearing.

Best practices

Do
  • Set maxLength to the exact code length; group long codes (e.g. 3 + 3) with a separator.
  • Use inputMode/pattern so mobile shows the right keyboard for numeric codes.
Don't
  • Don't use it for free text.
  • Don't omit a label — the slots aren't self-describing.

Content guidelines

  • Label the field with what the code is for (“Verification code”).
  • State the length and source (“6-digit code from your email”).

Troubleshooting

Slots render but nothing accepts input / wrong count.

Cause. Missing maxLength, or the number of InputOTPSlot indexes doesn't match it.

Fix. Set maxLength and render exactly that many InputOTPSlots with indexes 0…maxLength-1.

Accessibility

Role
textbox
Focus
The active slot shows the shared field-surface focus treatment (foreground/40 edge + neutral ring) and a blinking caret; focus stays on the single input.

Accessibility requirements

warn

Give the OTP input an accessible label (aria-label or a <label>).

when the InputOTP has no aria-label / aria-labelledby / associated label

Keyboard

0–9 / A–ZFill the active slot and advance.
BackspaceClear and step back.
PasteDistributes a pasted code across the slots.
A1.3.1Info and RelationshipsA4.1.2Name, Role, Value
  • A single underlying input drives the slots; label it (aria-label / a Label).