---
title: Input OTP
description: "Accessible one-time password input with joined character slots."
---

```tsx
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

```bash
npx shadcn@latest add @madeui/input-otp
```

## Usage

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

## Composition

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

## Separator

```tsx
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

```tsx
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

```tsx
'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](https://base-ui.com/react/components/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](https://base-ui.com/react/components/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.
