Input OTP
A segmented input for short fixed-length codes (one-time passwords, 2FA, PINs).
@garn/ui/input-otpOn 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.
npx garn-ui add input-otp- 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
| Selector | State | Description |
|---|---|---|
[data-slot=input-otp-slot] (active) | active-slot | The focused cell mirrors the shared field-surface focus recipe — edge to foreground/40 + the neutral ring — plus a fake caret. |
[aria-invalid=true] | invalid | aria-invalid on the hidden input turns the slot edges danger (and the active-slot ring danger), bridged via the container :has(). |
:disabled | disabled | Whole group dims; container has-[:disabled] handles it. |
| Property | Token | Tier |
|---|---|---|
slotSize | --garn-control-h-md | semantic · 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
- Set
maxLengthto 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 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
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–Z | Fill the active slot and advance. |
| Backspace | Clear and step back. |
| Paste | Distributes a pasted code across the slots. |
- A single underlying input drives the slots; label it (aria-label / a Label).