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.