Skip to content
Esc
navigateopen⌘Jpreview
On this page

Input OTP

Accessible one-time password input with joined character slots.

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

export default function InputOTPDemo() {
  return (
    <InputOTP length={6}>
      <InputOTPGroup>
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
      </InputOTPGroup>
      <InputOTPSeparator />
      <InputOTPGroup>
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
      </InputOTPGroup>
    </InputOTP>
  );
}

Install

npx shadcn@latest add @madeui/input-otp

Usage

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

Composition

<InputOTP length={6}>
  <InputOTPGroup>
    <InputOTPSlot />
    <InputOTPSlot />
    <InputOTPSlot />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot />
    <InputOTPSlot />
    <InputOTPSlot />
  </InputOTPGroup>
</InputOTP>

Separator

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

export default function InputOTPSeparatorExample() {
  return (
    <InputOTP length={8}>
      <InputOTPGroup>
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
      </InputOTPGroup>
      <InputOTPSeparator />
      <InputOTPGroup>
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
      </InputOTPGroup>
    </InputOTP>
  );
}

Disabled

import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';

export default function InputOTPDisabled() {
  return (
    <InputOTP length={6} disabled defaultValue="123456">
      <InputOTPGroup>
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
        <InputOTPSlot />
      </InputOTPGroup>
    </InputOTP>
  );
}

Controlled

Enter your code.

'use client';

import * as React from 'react';

import * as stylex from '@stylexjs/stylex';

import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
import { space, fontSize } from '@/lib/constants.stylex';
import { colors } from '@/lib/tokens.stylex';

export default function InputOTPControlled() {
  const [value, setValue] = React.useState('');

  return (
    <div {...stylex.props(styles.col)}>
      <InputOTP length={6} value={value} onValueChange={setValue}>
        <InputOTPGroup>
          <InputOTPSlot />
          <InputOTPSlot />
          <InputOTPSlot />
          <InputOTPSlot />
          <InputOTPSlot />
          <InputOTPSlot />
        </InputOTPGroup>
      </InputOTP>
      <p {...stylex.props(styles.value)}>
        {value ? `Entered: ${value}` : 'Enter your code.'}
      </p>
    </div>
  );
}

const styles = stylex.create({
  col: {
    alignItems: 'flex-start',
    display: 'flex',
    flexDirection: 'column',
    gap: space.s2,
  },
  value: {
    color: colors.mutedForeground,
    fontSize: fontSize.sm,
  },
});

API reference

Built on Base UI OTP Field — every slot is a real <input>, so focus, caret, and paste behavior come from the platform. All props are forwarded to the Base UI Root (length is required; value, onValueChange, mask, validationType, autoSubmit, …); see the Base UI OTP Field API reference. Each part also accepts a style prop (StyleXStyles, merged last). InputOTPSlot maps to a Base UI Input; slot order determines the character index.

Was this page helpful?