Propel

OTP Field

A one-time-password field — a row of character slots that owns focus movement, paste, and completion.

Character 1
Show code
import { OTPField } from "@makeplane/propel/components/otp-field";

export default function BasicDemo() {
  return <OTPField length={6} size="lg" aria-label="Verification code" />;
}

Installation

import { OTPField } from "@makeplane/propel/components/otp-field";

Usage

import { OTPField } from "@makeplane/propel/components/otp-field";

export default function BasicDemo() {
  return <OTPField length={6} size="lg" aria-label="Verification code" />;
}

Examples

Sizes

size sets the box size of every slot — lg, xl, and 2xl.

Character 1
Character 1
Character 1
Show code
import { OTPField, type OTPFieldInputSize } from "@makeplane/propel/components/otp-field";

const SIZES: OTPFieldInputSize[] = ["lg", "xl", "2xl"];

export default function SizesDemo() {
  return (
    <div className="flex flex-col items-start gap-4">
      {SIZES.map((size) => (
        <OTPField
          key={size}
          length={6}
          size={size}
          defaultValue="123456"
          aria-label="Verification code"
        />
      ))}
    </div>
  );
}

Grouped

groups splits the run with separators — [3, 3] renders the 123-456 shape. The counts must sum to length.

Character 1
Show code
import { OTPField } from "@makeplane/propel/components/otp-field";

export default function GroupedDemo() {
  return <OTPField length={6} size="lg" groups={[3, 3]} aria-label="Verification code" />;
}

Hint

hint shows helper text below the slots, e.g. a resend countdown. It’s replaced by error when set.

Character 1

Resend OTP in 15 seconds

Show code
import { OTPField } from "@makeplane/propel/components/otp-field";

export default function HintDemo() {
  return (
    <OTPField length={6} size="lg" hint="Resend OTP in 15 seconds" aria-label="Verification code" />
  );
}

Invalid

Passing error shows the message and flips every slot to the danger state.

Character 1
Code is invalid
Show code
import { OTPField } from "@makeplane/propel/components/otp-field";

export default function InvalidDemo() {
  return (
    <OTPField
      length={6}
      size="lg"
      defaultValue="12"
      error="Code is invalid"
      aria-label="Verification code"
    />
  );
}

Controlled

normalizeValue rewrites accepted characters while rejected ones surface through onValueInvalid, fed back as an error until the next valid keystroke clears it.

Character 1
Show code
import { OTPField } from "@makeplane/propel/components/otp-field";
import * as React from "react";

export default function ControlledDemo() {
  const [error, setError] = React.useState<React.ReactNode>(null);
  return (
    <OTPField
      length={6}
      size="lg"
      validationType="alphanumeric"
      normalizeValue={(value) => value.toUpperCase()}
      onValueChange={() => setError(null)}
      onValueInvalid={() => setError("Only letters and numbers are allowed")}
      error={error}
      aria-label="Recovery code"
    />
  );
}

API Reference

OTPField

The ready-made one-time-password / verification-code field: a row of `length` character slots. Drive it with `value`/`defaultValue` + `onValueChange`; the root owns focus movement, paste, and completion across the slots. Pass `mask` to obscure entered characters, `size` to set the box size, `hint` for helper text below the slots (e.g. a resend countdown), and `error` to show an error message instead — `Field.Root invalid` then propagates `data-invalid` to every slot, which recolors its border (and focus ring) to danger off that state. Grafts Base UI `OTPField` behavior onto the `elements/otp-field` styled parts (`OTPField` root + `OTPFieldInput`). Each slot resolves its index from the root context, so the ready-made simply renders one per slot; `groups` splits the run with separators (e.g. `groups={[3, 3]}` renders `123-456`).

PropTypeDefaultDescription
size(required)"lg" | "xl" | "2xl"Box size passed to every slot.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, OTPFieldRootState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.
hintReactNodeHelper text shown below the slots, e.g. a resend countdown. Replaced by `error` when set.
errorReactNodeError text shown below the slots; its presence flips every slot to the danger state.
groupsreadonly number[]Slot counts per visual group, separated by an `OTPFieldSeparator` (e.g. `[3, 3]` renders `123-456`). The counts must sum to `length`; omit for one flat run of slots.