Input OTP
---import { InputOtp, InputOtpGroup, InputOtpSlot,} from "@/components/starwind/input-otp";---
<InputOtp aria-label="Verification code" maxLength={6}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup></InputOtp>import { InputOtp, InputOtpGroup, InputOtpSlot,} from "@/components/starwind/input-otp";
export function Example() { return ( <InputOtp aria-label="Verification code" maxLength={6}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup> </InputOtp> );}Installation
pnpx starwind@latest add input-otpnpx starwind@latest add input-otpyarn dlx starwind@latest add input-otpUsage
General Notes
The Input OTP component provides a user-friendly way to input one-time passwords and verification codes. It supports keyboard navigation, paste handling, and pattern validation.
The essential components are InputOtp, InputOtpGroup, and InputOtpSlot. The InputOtpSeparator provides visual separation between groups of slots.
With Separator
Use InputOtpSeparator to visually group slots, commonly used for phone verification codes or formatted inputs.
---import { InputOtp, InputOtpGroup, InputOtpSeparator, InputOtpSlot,} from "@/components/starwind/input-otp";---
<InputOtp aria-label="Verification code with separator" maxLength={6}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> </InputOtpGroup> <InputOtpSeparator /> <InputOtpGroup> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup></InputOtp>import { InputOtp, InputOtpGroup, InputOtpSeparator, InputOtpSlot,} from "@/components/starwind/input-otp";
export function Example() { return ( <> <InputOtp aria-label="Verification code with separator" maxLength={6}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> </InputOtpGroup> <InputOtpSeparator /> <InputOtpGroup> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup> </InputOtp> </> );}Accessibility adjustment
Name the InputOtp root rather than its individual slots; Runtime exposes the shared name on the hidden input.
Alphanumeric Pattern
Use the pattern prop with REGEXP_ONLY_DIGITS_AND_CHARS to allow both letters and numbers.
---import { InputOtp, InputOtpGroup, InputOtpSlot, REGEXP_ONLY_DIGITS_AND_CHARS,} from "@/components/starwind/input-otp";---
<InputOtp aria-label="Alphanumeric verification code" maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup></InputOtp>import { InputOtp, InputOtpGroup, InputOtpSlot, REGEXP_ONLY_DIGITS_AND_CHARS,} from "@/components/starwind/input-otp";
export function Example() { return ( <> <InputOtp aria-label="Alphanumeric verification code" maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS} > <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup> </InputOtp> </> );}Accessibility adjustment
Keep an explicit accessible name when the pattern changes what characters the shared input accepts.
Sizes
Set size="sm", size="md" (the default), or size="lg" once on InputOtp. Every slot in the code field inherits that physical visual scale; InputOtpSlot no longer accepts size.
Small
Medium (default)
Large
---import { InputOtp, InputOtpGroup, InputOtpSlot,} from "@/components/starwind/input-otp";---
<div class="flex flex-col gap-6"> <div class="space-y-2"> <p class="text-muted-foreground text-sm">Small</p> <InputOtp size="sm" aria-label="Small verification code" maxLength={4}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> </InputOtpGroup> </InputOtp> </div> <div class="space-y-2"> <p class="text-muted-foreground text-sm">Medium (default)</p> <InputOtp size="md" aria-label="Medium verification code" maxLength={4}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> </InputOtpGroup> </InputOtp> </div> <div class="space-y-2"> <p class="text-muted-foreground text-sm">Large</p> <InputOtp size="lg" aria-label="Large verification code" maxLength={4}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> </InputOtpGroup> </InputOtp> </div></div>import { InputOtp, InputOtpGroup, InputOtpSlot,} from "@/components/starwind/input-otp";
export function Example() { return ( <> <div className="flex flex-col gap-6"> <div className="space-y-2"> <p className="text-muted-foreground text-sm">Small</p> <InputOtp size="sm" aria-label="Small verification code" maxLength={4}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> </InputOtpGroup> </InputOtp> </div> <div className="space-y-2"> <p className="text-muted-foreground text-sm">Medium (default)</p> <InputOtp size="md" aria-label="Medium verification code" maxLength={4}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> </InputOtpGroup> </InputOtp> </div> <div className="space-y-2"> <p className="text-muted-foreground text-sm">Large</p> <InputOtp size="lg" aria-label="Large verification code" maxLength={4}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> </InputOtpGroup> </InputOtp> </div> </div> </> );}Accessibility adjustment
Size is visual, so each preview uses a distinct accessible name instead of relying on the nearby size caption.
Disabled
Use the disabled prop to prevent user interaction.
---import { InputOtp, InputOtpGroup, InputOtpSlot,} from "@/components/starwind/input-otp";---
<InputOtp aria-label="Verification code" maxLength={6} disabled> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup></InputOtp>import { InputOtp, InputOtpGroup, InputOtpSlot,} from "@/components/starwind/input-otp";
export function Example() { return ( <> <InputOtp aria-label="Verification code" maxLength={6} disabled> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup> </InputOtp> </> );}Accessibility adjustment
Disabled controls still need an accessible name so assistive technology can identify them.
Form Submission
The component integrates with native HTML forms using the name prop. The hidden input stores the complete OTP value.
---import { Button } from "@/components/starwind/button";import { InputOtp, InputOtpGroup, InputOtpSlot,} from "@/components/starwind/input-otp";import { Label } from "@/components/starwind/label";---
<form id="otp-demo-form" class="space-y-4"> <div class="space-y-2"> <Label for="otp-form">Verification Code</Label> <InputOtp id="otp-form" name="otp" maxLength={6} required> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup> </InputOtp> </div> <Button type="submit">Submit Code</Button></form><div id="form-result" class="bg-muted mt-4 hidden rounded-md p-4 font-mono text-sm"></div>
<script> const form = document.getElementById("otp-demo-form") as HTMLFormElement; const resultDiv = document.getElementById("form-result");
if (form && resultDiv) { form.addEventListener("submit", (e) => { e.preventDefault(); const formData = new FormData(form); const otp = formData.get("otp");
resultDiv.textContent = `Submitted OTP: ${otp}`; resultDiv.classList.remove("hidden"); }); }</script>import type { FormEvent } from "react";import { useState } from "react";import { Button } from "@/components/starwind/button";import { InputOtp, InputOtpGroup, InputOtpSlot } from "@/components/starwind/input-otp";import { Label } from "@/components/starwind/label";
export function Example() { const [submittedOtp, setSubmittedOtp] = useState<string | null>(null);
function handleSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); setSubmittedOtp(String(new FormData(event.currentTarget).get("otp") ?? "")); }
return ( <> <form className="space-y-4" onSubmit={handleSubmit}> <div className="space-y-2"> <Label htmlFor="otp-form">Verification Code</Label> <InputOtp id="otp-form" name="otp" maxLength={6} required> <InputOtpGroup> {Array.from({ length: 6 }, (_, index) => ( <InputOtpSlot key={index} index={index} /> ))} </InputOtpGroup> </InputOtp> </div> <Button type="submit">Submit Code</Button> </form> {submittedOtp !== null ? ( <div className="bg-muted mt-4 rounded-md p-4 font-mono text-sm"> Submitted OTP: {submittedOtp} </div> ) : null} </> );}Value changes
Listen on the Input OTP root for starwind:value-change. Its detail includes value, previousValue, reason, and inputOtpId; the event is cancelable before Runtime commits the change.
<InputOtp id="verification-code" aria-label="Verification code" maxLength={6}> <InputOtpGroup> <InputOtpSlot index={0} /> <InputOtpSlot index={1} /> <InputOtpSlot index={2} /> <InputOtpSlot index={3} /> <InputOtpSlot index={4} /> <InputOtpSlot index={5} /> </InputOtpGroup></InputOtp>
<script> const root = document.querySelector<HTMLElement>("#verification-code"); root?.addEventListener("starwind:value-change", (event) => { const { value, reason } = ( event as CustomEvent<{ value: string; reason: string }> ).detail; console.log(value, reason); });</script>import { InputOtp, InputOtpGroup, InputOtpSlot } from "@/components/starwind/input-otp";
export function Example() { return ( <InputOtp aria-label="Verification code" maxLength={6} onValueChange={(value, details) => console.log(value, details.reason)} > <InputOtpGroup> {Array.from({ length: 6 }, (_, index) => ( <InputOtpSlot key={index} index={index} /> ))} </InputOtpGroup> </InputOtp> );}Size migration
| Before | After |
|---|---|
<InputOtp><InputOtpSlot index={0} size="lg" /></InputOtp> | <InputOtp size="lg"><InputOtpSlot index={0} /></InputOtp> |
API Reference
Styled Component API
These props are added or materially changed by the installed styled component. Standard HTML attributes remain available through the inherited interfaces noted below. Expand a prop to see named type definitions and framework-specific imports. Follow the Primitive and Runtime links for lower-level behavior props.
Input Otp
Inherits div attributes. Omits `defaultValue`, `id`, `onChange`, `pattern`, and `value`.
Contains the following additional props:
size "sm" | "md" | "lg" "md"
- Description
- Selects the component's visual size.
- Classification
- Wrapper prop
Primitive And Runtime API
Use these references when you need the lower-level behavior APIs behind Input Otp.
Primitive API
Runtime API
- Input OTP primitive
createInputOtpfrom@starwind-ui/runtime/input-otp
Changelog
v3.0.1
- Named the generated aggregate default export so React and Astro tooling can identify the installed component cleanly.
v3.0.0
- Moved slot sizing from
InputOtpSlotto theInputOtproot, withmdas the default. - Migration:
<InputOtpSlot index={0} size="sm" />becomes<InputOtp size="sm"><InputOtpSlot index={0} /></InputOtp>.
v2.0.0
- Rebuilt Input OTP on Starwind Runtime for value state, form participation, paste handling, and value-change events.
- See the Input OTP Primitive for the underlying unstyled anatomy and behavior API.
- Input OTP now delegates keyboard navigation, paste distribution, pattern filtering, hidden form input synchronization, and reset behavior to the Runtime. Give the root an accessible name because its visible slots share one hidden input.
v1.0.3
- Refactor tailwind variants functions into separate
variants.tsfile
v1.0.1
- Auto focus hidden input on click to automatically open keyboards on mobile devices
- Update component to always focus the next empty index on click
v1.0.0
- Initial release with starwind v1.15.0