1
ComponentsForm Controls

OTP Field

Fixed-length segmented input for one-time passwords, verification codes and recovery codes.

Purpose

Use OTP Field for short one-time passwords, verification codes and recovery codes that should be entered as a fixed sequence of slots.

Six-digit code

import { OTPField, OTPFieldInput, OTPFieldSeparator } from "@/components/ui/otp-field";
import { Field, FieldLabel } from "@/components/ui/field";

export function Example() {
  return (
    <Field>
      <FieldLabel>Verification code</FieldLabel>
      <OTPField length={6}>
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldSeparator />
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
      </OTPField>
    </Field>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/otp-field

Import

import { OTPField, OTPFieldInput, OTPFieldSeparator } from "@/components/ui/otp-field";
import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";

Need the machine-readable source metadata? View registry JSON.

Usage

OTP Field is for fixed-length verification codes. Use separators only when they improve grouping.

import { OTPField, OTPFieldInput, OTPFieldSeparator } from "@/components/ui/otp-field";
import { Field, FieldLabel } from "@/components/ui/field";

export function Example() {
  return (
    <Field>
      <FieldLabel>Verification code</FieldLabel>
      <OTPField length={6}>
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldSeparator />
        <OTPFieldInput />
        <OTPFieldInput />
        <OTPFieldInput />
      </OTPField>
    </Field>
  );
}

Examples

Sizes, states, masking, validation types and normalization are props and compositions of the same primitive.

Sizes

<OTPField aria-label="Small code" length={4} size="sm">...</OTPField>
<OTPField aria-label="Default code" length={4}>...</OTPField>
<OTPField aria-label="Large code" length={4} size="lg">...</OTPField>
<OTPField aria-label="Extra large code" length={4} size="xl">...</OTPField>

States

disabled dims the whole field; aria-invalid on the inputs marks a rejected code.

{/* Disabled */}
<OTPField aria-label="Disabled verification code" defaultValue="123456" disabled length={6}>
  <OTPFieldInput />
  {/* ... */}
</OTPField>

{/* Invalid */}
<OTPField aria-label="Invalid verification code" defaultValue="1234" length={6}>
  <OTPFieldInput aria-invalid />
  {/* ... */}
</OTPField>

Masked entry

Use mask to obscure the code on shared screens.

<Field>
  <FieldLabel>Access code</FieldLabel>
  <OTPField length={6} mask>
    <OTPFieldInput />
    {/* ... */}
  </OTPField>
  <FieldDescription>
    Use mask to obscure the code on shared screens.
  </FieldDescription>
</Field>

Alphanumeric

Accepts letters and numbers for backup codes such as A7C9XZ.

<Field>
  <FieldLabel>Recovery code</FieldLabel>
  <OTPField length={6} validationType="alphanumeric">
    <OTPFieldInput />
    {/* ... */}
  </OTPField>
  <FieldDescription>
    Accepts letters and numbers for backup codes such as A7C9XZ.
  </FieldDescription>
</Field>

Placeholder hints

Placeholder hints stay visible until the slot is focused.

<OTPField length={6}>
  <OTPFieldInput
    className="placeholder:text-muted-foreground focus-visible:placeholder:text-transparent"
    placeholder="-"
  />
  {/* ... */}
</OTPField>

Validation

Enter 123456 to pass validation.

const [value, setValue] = useState("");
const [invalid, setInvalid] = useState(false);
const valid = value.length === 6 && value === "123456";

<Field>
  <FieldLabel>Verification code</FieldLabel>
  <OTPField
    length={6}
    onValueChange={(nextValue) => {
      setValue(nextValue);
      setInvalid(nextValue.length === 6 ? nextValue !== "123456" : false);
    }}
    value={value}
  >
    <OTPFieldInput aria-invalid={invalid || undefined} />
    {/* ... */}
  </OTPField>
  {invalid ? <FieldError>Code must be 123456.</FieldError> : null}
  {valid ? <FieldDescription>Code verified.</FieldDescription> : null}
</Field>

Custom normalization

normalizeValue filters accepted characters; onValueInvalid reports rejected input.

Digits 0-3 only.

function normalizeTierCode(value: string) {
  return value.replace(/[^0-3]/g, "");
}

<OTPField
  inputMode="numeric"
  length={6}
  normalizeValue={normalizeTierCode}
  onValueChange={setValue}
  onValueInvalid={showInvalidMessage}
  validationType="none"
  value={value}
>
  <OTPFieldInput />
  {/* ... */}
</OTPField>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • OTPField is the root and requires length; it also accepts size="sm" | "default" | "lg" | "xl" and the Base UI OTP Field props.
  • size="xl" renders each slot as a 56px square; the segmented value remains a set of real inputs. Use the surrounding provider's radius="sm" personality for the global 8px main corner.
  • Render one OTPFieldInput for every character in DOM order. The number of slots must match length.
  • OTPFieldSeparator visually divides slot groups without changing the value length.
  • Use validationType, normalizeValue, onValueInvalid, mask and controlled or uncontrolled value props on the root.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Slots do not translate or resize while the value changes. Focus, invalid and disabled feedback use the native state styles.
  • Reduced motion needs no extra handling because OTP Field does not animate with scale or movement.

Accessibility

  • Pair the field with a visible FieldLabel; without one, give every slot a specific label such as Character 2 of 6.
  • Keep the slot count synchronized with length and explain rejected characters when custom validation is active.
  • Masking protects shoulder-surfing only; it is not a substitute for secure verification logic.

Implementation Guidance

  • Use OTP Field only for short fixed-length codes, not free text.
  • Keep every slot as a real OTPFieldInput and use length, not the legacy maxLength contract.
  • Do not replace the native slot, focus, disabled, invalid or masking behavior.

On This Page