diff --git a/docs/data/base/components/input/OTPInput.js b/docs/data/base/components/input/OTPInput.js
new file mode 100644
index 00000000000000..4b3e5512147af8
--- /dev/null
+++ b/docs/data/base/components/input/OTPInput.js
@@ -0,0 +1,231 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import { Input as BaseInput } from '@mui/base/Input';
+import { Box, styled } from '@mui/system';
+
+function OTP({ separator, length, value, onChange }) {
+ const inputRefs = React.useRef(new Array(length).fill(null));
+
+ const focusInput = (targetIndex) => {
+ const targetInput = inputRefs.current[targetIndex];
+ targetInput.focus();
+ };
+
+ const selectInput = (targetIndex) => {
+ const targetInput = inputRefs.current[targetIndex];
+ targetInput.select();
+ };
+
+ const handleKeyDown = (event, currentIndex) => {
+ switch (event.key) {
+ case 'ArrowUp':
+ case 'ArrowDown':
+ case ' ':
+ event.preventDefault();
+ break;
+ case 'ArrowLeft':
+ event.preventDefault();
+ if (currentIndex > 0) {
+ focusInput(currentIndex - 1);
+ selectInput(currentIndex - 1);
+ }
+ break;
+ case 'ArrowRight':
+ event.preventDefault();
+ if (currentIndex < length - 1) {
+ focusInput(currentIndex + 1);
+ selectInput(currentIndex + 1);
+ }
+ break;
+ case 'Delete':
+ event.preventDefault();
+ onChange((prevOtp) => {
+ const otp =
+ prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
+ return otp;
+ });
+
+ break;
+ case 'Backspace':
+ event.preventDefault();
+ if (currentIndex > 0) {
+ focusInput(currentIndex - 1);
+ selectInput(currentIndex - 1);
+ }
+
+ onChange((prevOtp) => {
+ const otp =
+ prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
+ return otp;
+ });
+ break;
+
+ default:
+ break;
+ }
+ };
+
+ const handleChange = (event, currentIndex) => {
+ const currentValue = event.target.value;
+ let indexToEnter = 0;
+
+ while (indexToEnter <= currentIndex) {
+ if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
+ indexToEnter += 1;
+ } else {
+ break;
+ }
+ }
+ onChange((prev) => {
+ const otpArray = prev.split('');
+ const lastValue = currentValue[currentValue.length - 1];
+ otpArray[indexToEnter] = lastValue;
+ return otpArray.join('');
+ });
+ if (currentValue !== '') {
+ if (currentIndex < length - 1) {
+ focusInput(currentIndex + 1);
+ }
+ }
+ };
+
+ const handleClick = (event, currentIndex) => {
+ selectInput(currentIndex);
+ };
+
+ const handlePaste = (event, currentIndex) => {
+ event.preventDefault();
+ const clipboardData = event.clipboardData;
+
+ // Check if there is text data in the clipboard
+ if (clipboardData.types.includes('text/plain')) {
+ let pastedText = clipboardData.getData('text/plain');
+ pastedText = pastedText.substring(0, length).trim();
+ let indexToEnter = 0;
+
+ while (indexToEnter <= currentIndex) {
+ if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
+ indexToEnter += 1;
+ } else {
+ break;
+ }
+ }
+
+ const otpArray = value.split('');
+
+ for (let i = indexToEnter; i < length; i += 1) {
+ const lastValue = pastedText[i - indexToEnter] ?? ' ';
+ otpArray[i] = lastValue;
+ }
+
+ onChange(otpArray.join(''));
+ }
+ };
+
+ return (
+
+ {new Array(length).fill(null).map((_, index) => (
+
+ {
+ inputRefs.current[index] = ele;
+ },
+ onKeyDown: (event) => handleKeyDown(event, index),
+ onChange: (event) => handleChange(event, index),
+ onClick: (event) => handleClick(event, index),
+ onPaste: (event) => handlePaste(event, index),
+ value: value[index] ?? '',
+ },
+ }}
+ />
+ {index === length - 1 ? null : separator}
+
+ ))}
+
+ );
+}
+
+OTP.propTypes = {
+ length: PropTypes.number.isRequired,
+ onChange: PropTypes.func.isRequired,
+ separator: PropTypes.node,
+ value: PropTypes.string.isRequired,
+};
+
+export default function OTPInput() {
+ const [otp, setOtp] = React.useState('');
+
+ return (
+
+ -} value={otp} onChange={setOtp} length={5} />
+ Entered value: {otp}
+
+ );
+}
+
+const blue = {
+ 100: '#DAECFF',
+ 200: '#80BFFF',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E5',
+ 700: '#0059B2',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+const InputElement = styled('input')(
+ ({ theme }) => `
+ width: 40px;
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ font-weight: 400;
+ line-height: 1.5;
+ padding: 8px 0px;
+ border-radius: 8px;
+ text-align: center;
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ box-shadow: 0px 2px 4px ${
+ theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
+ };
+
+ &:hover {
+ border-color: ${blue[400]};
+ }
+
+ &:focus {
+ border-color: ${blue[400]};
+ box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
+ }
+
+ // firefox
+ &:focus-visible {
+ outline: 0;
+ }
+`,
+);
diff --git a/docs/data/base/components/input/OTPInput.tsx b/docs/data/base/components/input/OTPInput.tsx
new file mode 100644
index 00000000000000..16342a4935cf6d
--- /dev/null
+++ b/docs/data/base/components/input/OTPInput.tsx
@@ -0,0 +1,245 @@
+import * as React from 'react';
+import { Input as BaseInput } from '@mui/base/Input';
+import { Box, styled } from '@mui/system';
+
+function OTP({
+ separator,
+ length,
+ value,
+ onChange,
+}: {
+ separator: React.ReactNode;
+ length: number;
+ value: string;
+ onChange: React.Dispatch>;
+}) {
+ const inputRefs = React.useRef(new Array(length).fill(null));
+
+ const focusInput = (targetIndex: number) => {
+ const targetInput = inputRefs.current[targetIndex];
+ targetInput.focus();
+ };
+
+ const selectInput = (targetIndex: number) => {
+ const targetInput = inputRefs.current[targetIndex];
+ targetInput.select();
+ };
+
+ const handleKeyDown = (
+ event: React.KeyboardEvent,
+ currentIndex: number,
+ ) => {
+ switch (event.key) {
+ case 'ArrowUp':
+ case 'ArrowDown':
+ case ' ':
+ event.preventDefault();
+ break;
+ case 'ArrowLeft':
+ event.preventDefault();
+ if (currentIndex > 0) {
+ focusInput(currentIndex - 1);
+ selectInput(currentIndex - 1);
+ }
+ break;
+ case 'ArrowRight':
+ event.preventDefault();
+ if (currentIndex < length - 1) {
+ focusInput(currentIndex + 1);
+ selectInput(currentIndex + 1);
+ }
+ break;
+ case 'Delete':
+ event.preventDefault();
+ onChange((prevOtp) => {
+ const otp =
+ prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
+ return otp;
+ });
+
+ break;
+ case 'Backspace':
+ event.preventDefault();
+ if (currentIndex > 0) {
+ focusInput(currentIndex - 1);
+ selectInput(currentIndex - 1);
+ }
+
+ onChange((prevOtp) => {
+ const otp =
+ prevOtp.slice(0, currentIndex) + prevOtp.slice(currentIndex + 1);
+ return otp;
+ });
+ break;
+
+ default:
+ break;
+ }
+ };
+
+ const handleChange = (
+ event: React.ChangeEvent,
+ currentIndex: number,
+ ) => {
+ const currentValue = event.target.value;
+ let indexToEnter = 0;
+
+ while (indexToEnter <= currentIndex) {
+ if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
+ indexToEnter += 1;
+ } else {
+ break;
+ }
+ }
+ onChange((prev) => {
+ const otpArray = prev.split('');
+ const lastValue = currentValue[currentValue.length - 1];
+ otpArray[indexToEnter] = lastValue;
+ return otpArray.join('');
+ });
+ if (currentValue !== '') {
+ if (currentIndex < length - 1) {
+ focusInput(currentIndex + 1);
+ }
+ }
+ };
+
+ const handleClick = (
+ event: React.MouseEvent,
+ currentIndex: number,
+ ) => {
+ selectInput(currentIndex);
+ };
+
+ const handlePaste = (
+ event: React.ClipboardEvent,
+ currentIndex: number,
+ ) => {
+ event.preventDefault();
+ const clipboardData = event.clipboardData;
+
+ // Check if there is text data in the clipboard
+ if (clipboardData.types.includes('text/plain')) {
+ let pastedText = clipboardData.getData('text/plain');
+ pastedText = pastedText.substring(0, length).trim();
+ let indexToEnter = 0;
+
+ while (indexToEnter <= currentIndex) {
+ if (inputRefs.current[indexToEnter].value && indexToEnter < currentIndex) {
+ indexToEnter += 1;
+ } else {
+ break;
+ }
+ }
+
+ const otpArray = value.split('');
+
+ for (let i = indexToEnter; i < length; i += 1) {
+ const lastValue = pastedText[i - indexToEnter] ?? ' ';
+ otpArray[i] = lastValue;
+ }
+
+ onChange(otpArray.join(''));
+ }
+ };
+
+ return (
+
+ {new Array(length).fill(null).map((_, index) => (
+
+ {
+ inputRefs.current[index] = ele!;
+ },
+ onKeyDown: (event) => handleKeyDown(event, index),
+ onChange: (event) => handleChange(event, index),
+ onClick: (event) => handleClick(event, index),
+ onPaste: (event) => handlePaste(event, index),
+ value: value[index] ?? '',
+ },
+ }}
+ />
+ {index === length - 1 ? null : separator}
+
+ ))}
+
+ );
+}
+
+export default function OTPInput() {
+ const [otp, setOtp] = React.useState('');
+
+ return (
+
+ -} value={otp} onChange={setOtp} length={5} />
+ Entered value: {otp}
+
+ );
+}
+
+const blue = {
+ 100: '#DAECFF',
+ 200: '#80BFFF',
+ 400: '#3399FF',
+ 500: '#007FFF',
+ 600: '#0072E5',
+ 700: '#0059B2',
+};
+
+const grey = {
+ 50: '#F3F6F9',
+ 100: '#E5EAF2',
+ 200: '#DAE2ED',
+ 300: '#C7D0DD',
+ 400: '#B0B8C4',
+ 500: '#9DA8B7',
+ 600: '#6B7A90',
+ 700: '#434D5B',
+ 800: '#303740',
+ 900: '#1C2025',
+};
+
+const InputElement = styled('input')(
+ ({ theme }) => `
+ width: 40px;
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: 0.875rem;
+ font-weight: 400;
+ line-height: 1.5;
+ padding: 8px 0px;
+ border-radius: 8px;
+ text-align: center;
+ color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
+ background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'};
+ border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
+ box-shadow: 0px 2px 4px ${
+ theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'
+ };
+
+ &:hover {
+ border-color: ${blue[400]};
+ }
+
+ &:focus {
+ border-color: ${blue[400]};
+ box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[600] : blue[200]};
+ }
+
+ // firefox
+ &:focus-visible {
+ outline: 0;
+ }
+`,
+);
diff --git a/docs/data/base/components/input/OTPInput.tsx.preview b/docs/data/base/components/input/OTPInput.tsx.preview
new file mode 100644
index 00000000000000..9e5f02b24737cc
--- /dev/null
+++ b/docs/data/base/components/input/OTPInput.tsx.preview
@@ -0,0 +1,2 @@
+-} value={otp} onChange={setOtp} length={5} />
+Entered value: {otp}
\ No newline at end of file
diff --git a/docs/data/base/components/input/input.md b/docs/data/base/components/input/input.md
index 3e0ef2a7679d5d..5134081bc1f995 100644
--- a/docs/data/base/components/input/input.md
+++ b/docs/data/base/components/input/input.md
@@ -134,3 +134,11 @@ To set minimum and maximum sizes, add the `minRows` and `maxRows` props.
The following demo shows how to insert a Textarea Autosize component into an Input so that its height grows with the length of the content:
{{"demo": "InputMultilineAutosize.js"}}
+
+## Common examples
+
+### OTP Input
+
+The following demo shows how to build a one-time password component using `Input`.
+
+{{"demo": "OTPInput.js"}}