<div id="root"></div>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");

.base {
  margin: 0;
  padding: 0;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: "Inter", sans-serif;
  h1 {
    font-size: 1.2rem;
  }
  .otp-base {
    width: auto;
    display: flex;
    align-items: center;
    justify-content: space-evenly;
    gap: 0.5rem;
    input {
      width: 1ch;
      padding: 1rem;
      border-top:0px solid transparent;
      border-left:0px solid transparent;
      border-right:0px solid transparent;
      border-bottom: 3px solid rgba(0,0,0,0.5);
      outline: none;
      font-size: 2rem;
      transition:all .2s ease;
      &:focus {
        border-bottom: 3px solid #574b90;
      }
    }
  }

  .button{
      visibility:hidden;
    position:absolute;
    margin-top:2rem;
    right:0;
    text-transform:uppercase;
    padding:.4rem;
    background:none;
    border:1px solid #574b90;
    border-radius:5px;
    cursor:pointer;
    transition:all .2s ease;
      &.show{
        visibility:visible;
      }
    &:hover{
      background:#574b90;
      color:#fff;
    }
    }
  
  .otp-error {
    animation: shake .3s ease forwards;
    pointer-events:none;
    input {
      border-bottom: 2px solid red;
      pointer-events:none;
      &:focus{
        border-bottom: 2px solid red;
      }
    }
   
  }
}

// animations - shake

@keyframes shake {
  0% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(5px);
  }
  50% {
    transform: translateX(-5px);
  }
  75% {
    transform: translateX(5px);
  }
  100% {
    transform: translateX(0);
  }
}
View Compiled
const { useState, useEffect, useRef } = React;

const App = () => {
  const [otp, newOtp] = useState();
  const [verfied, setVerfied] = useState(false);
  const [otpVal, setOtpVal] = useState([]);
  const textBase = useRef(null);

  // generate random otp for each first render

  useEffect(() => {
    newOtp(Math.floor(1000 + Math.random() * 9000));
  }, []);

  const clearAll = () => {
    textBase.current.classList.remove("otp-error");
    textBase.current.childNodes.forEach((child) => {
      child.value = "";
    });
    setOtpVal([]);
    setVerfied(false);
  };

  const getOtp = () => {
    if (parseInt(otpVal.join("")) === otp) {
      textBase.current.classList.remove("otp-error");
      setVerfied(true);
    } else {
      textBase.current.classList.add("otp-error");
    }
  };

  const focusNext = (e) => {
    const childCount = textBase.current.childElementCount;
    const currentIndex = [...e.target.parentNode.children].indexOf(e.target);
    if (currentIndex !== childCount - 1) {
      e.target.nextSibling.focus();
    } else {
      const values = [];
      textBase.current.childNodes.forEach((child) => {
        values.push(child.value);
      });
      if (values.length !== 0) {
        setOtpVal(values);
      }
    }
  };

  useEffect(() => {
    if (otpVal.length === textBase.current.childElementCount) {
      getOtp();
    }
  }, [otpVal]);

  return (
    <div className="base">
      {!verfied ? (
        <>
          <h1> Enter OTP : {otp}</h1>
          <div className="otp-base" ref={textBase}>
            {new Array(4).fill(null).map((input) => {
              return <input type="text" onChange={(e) => focusNext(e)} />;
            })}
          </div>

          <button
            className={`button ${otpVal.length > 0 && "show"}`}
            onClick={() => clearAll()}
          >
            clear otp
          </button>
        </>
      ) : (
        <> verified</>
      )}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/react-transition-group/4.4.2/react-transition-group.min.js