Component Library

← Back to gallery

Verification code input

Category: Authentication · Tags: otp, one-time-code, two-factor, verification, resend

Enter your code

We sent a 6-digit code to your email. It expires in 10 minutes.

Did not get it?

How to use it

Use for a single six-digit code sent by email or text message during sign-in or verification. Prefer this one input over six separate boxes, which break paste and autofill. The handlers only show demo messages: the developer must wire Verify and Resend to the real endpoints, set the pattern and maxlength to the real code length, change the 30-second cooldown if the server uses another, and rename the dcl-otp ids if it appears twice.

Accessibility

One native text input with inputmode="numeric" (number keypad on phones), autocomplete="one-time-code" (the code can be filled from a text message) and pattern="[0-9]{6}", labelled with <label for> and described by the instruction through aria-describedby. Resend is a native <button> that uses aria-disabled during the cooldown, so it stays focusable and its countdown text stays readable; results go to a role="status" region. Text #0f172a on white is 17.85:1, the hint #475569 on white is 7.58:1, the resend text #1d4ed8 on white is 6.70:1 and #475569 on #f1f5f9 during the cooldown is 6.92:1, and the verify button's white on #1d4ed8 (#1e40af hovered) is 6.70:1 (8.72:1).

Source (copy-ready)

<form class="dcl-otp" aria-labelledby="dcl-otp-title">
  <h2 id="dcl-otp-title" class="dcl-otp__title">Enter your code</h2>
  <label for="dcl-otp-code" class="dcl-otp__label">Verification code</label>
  <p id="dcl-otp-hint" class="dcl-otp__hint">We sent a 6-digit code to your email. It expires in 10 minutes.</p>
  <input type="text" id="dcl-otp-code" name="code" class="dcl-otp__input" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" maxlength="6" required aria-describedby="dcl-otp-hint">
  <button type="submit" class="dcl-otp__verify">Verify</button>
  <div class="dcl-otp__resend-row">
    <span class="dcl-otp__question">Did not get it?</span>
    <button type="button" class="dcl-otp__resend" aria-disabled="true" data-cooldown="30">Resend code in 30 s</button>
  </div>
  <p class="dcl-otp__status" role="status"></p>
</form>
<style>
.dcl-otp { display: flex; flex-direction: column; gap: 0.6rem; max-width: 22rem; padding: 1.5rem; border: 1px solid #cbd5e1; border-radius: 0.75rem; font: inherit; color: #0f172a; background-color: #ffffff; box-sizing: border-box; }
.dcl-otp__title { margin: 0; font-size: 1.375rem; color: #0f172a; background-color: #ffffff; }
.dcl-otp__label { font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-otp__hint { margin: 0; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-otp__input { width: 100%; max-width: 12rem; box-sizing: border-box; padding: 0.55rem 0.65rem; border: 1px solid #64748b; border-radius: 6px; font: inherit; font-size: 1.375rem; letter-spacing: 0.3em; font-variant-numeric: tabular-nums; color: #0f172a; background-color: #ffffff; }
.dcl-otp__input:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-otp__verify { padding: 0.65rem 1rem; border: none; border-radius: 6px; font: inherit; font-weight: 600; cursor: pointer; color: #ffffff; background-color: #1d4ed8; }
.dcl-otp__verify:hover { color: #ffffff; background-color: #1e40af; }
.dcl-otp__verify:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-otp__resend-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.dcl-otp__question { color: #334155; background-color: #ffffff; }
.dcl-otp__resend { padding: 0.35rem 0.6rem; border: 1px solid #94a3b8; border-radius: 6px; font: inherit; cursor: pointer; color: #1d4ed8; background-color: #ffffff; }
.dcl-otp__resend:hover { color: #1e40af; background-color: #eff6ff; }
.dcl-otp__resend:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-otp__resend[aria-disabled="true"] { cursor: not-allowed; color: #475569; background-color: #f1f5f9; }
.dcl-otp__status { margin: 0; font-size: 0.9rem; color: #334155; background-color: #ffffff; }
</style>
<script>
(function () {
  document.querySelectorAll(".dcl-otp").forEach(function (form) {
    if (form.dataset.dclBound) {
      return;
    }
    form.dataset.dclBound = "true";
    var resend = form.querySelector(".dcl-otp__resend");
    var status = form.querySelector(".dcl-otp__status");
    var timer = null;
    function say(text) {
      if (status) {
        status.textContent = text;
      }
    }
    function startCooldown() {
      if (!resend) {
        return;
      }
      var left = parseInt(resend.getAttribute("data-cooldown"), 10) || 30;
      resend.setAttribute("aria-disabled", "true");
      resend.textContent = "Resend code in " + left + " s";
      clearInterval(timer);
      timer = setInterval(function () {
        left -= 1;
        if (left <= 0) {
          clearInterval(timer);
          resend.setAttribute("aria-disabled", "false");
          resend.textContent = "Resend code";
          return;
        }
        resend.textContent = "Resend code in " + left + " s";
      }, 1000);
    }
    if (resend) {
      resend.addEventListener("click", function () {
        if (resend.getAttribute("aria-disabled") === "true") {
          return;
        }
        say("A new code is on its way. (Demo only: nothing was sent.)");
        startCooldown();
      });
    }
    form.addEventListener("submit", function (event) {
      event.preventDefault();
      say("Demo only: the code was not checked. Connect this form to your verification endpoint.");
    });
    startCooldown();
  });
})();
</script>

Was this useful? Send feedback.