Source Code

<div class="ov-card">
  <div class="ov-icon">
    <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#4f46e5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>
  </div>
  <h2>Enter the 6-digit code</h2>
  <p class="ov-sub">We sent a verification code to your phone ending in <strong>&bull;&bull;42</strong>.</p>

  <div class="ov-otp-row" id="ovOtpRow">
    <input type="text" inputmode="numeric" maxlength="1" class="ov-otp-box" data-index="0" />
    <input type="text" inputmode="numeric" maxlength="1" class="ov-otp-box" data-index="1" />
    <input type="text" inputmode="numeric" maxlength="1" class="ov-otp-box" data-index="2" />
    <input type="text" inputmode="numeric" maxlength="1" class="ov-otp-box" data-index="3" />
    <input type="text" inputmode="numeric" maxlength="1" class="ov-otp-box" data-index="4" />
    <input type="text" inputmode="numeric" maxlength="1" class="ov-otp-box" data-index="5" />
  </div>

  <p class="ov-error" id="ovError"></p>

  <button class="ov-verify-btn" id="ovVerifyBtn">Verify</button>

  <p class="ov-resend">
    Didn't get a code?
    <button class="ov-resend-btn" id="ovResendBtn" disabled>Resend code (<span id="ovCountdown">30</span>s)</button>
  </p>
</div>

Two-Factor OTP Verification Screen — Free HTML CSS JS Snippet

Two-Factor OTP Verification Screen · Forms · Plain HTML, CSS & JS · Live preview

What's included

Features

Six individual OTP boxes with automatic focus-advance as each digit is typed
Backspace on an empty box moves focus back to the previous box
Pasting a full 6-digit code splits it across all boxes in one paste action
Non-digit characters are stripped from both typed and pasted input
Live 30-second countdown disables the Resend link until it expires
Resend action clears all boxes, refocuses the first one, and restarts the countdown
Inline error message shown if Verify is clicked before all six digits are entered
Verify button gives immediate visual confirmation once a full code is submitted

About this UI Snippet

Two-Factor OTP Verification Screen — Auto-Advancing 6-Digit Code Input

Screenshot of the Two-Factor OTP Verification Screen snippet rendered live

Two-factor and OTP verification screens live or die on the details of their input handling. This snippet implements the full expected interaction set for a 6-box digit input: auto-advancing focus as digits are typed, backspace navigating back to the previous box, full-code paste support that splits a pasted 6-digit string across all boxes at once, and a Resend code link gated behind a live 30-second countdown.

Auto-advance and backspace navigation

Each of the six inputs listens for its own input event, strips any non-digit character, and — if a digit was entered — moves focus to the next box automatically via .focus(). A separate keydown listener watches for Backspace on an already-empty box and moves focus back to the previous one, matching the behavior users expect from native OTP inputs on mobile keyboards.

Splitting a pasted code across all boxes

The paste event is intercepted with preventDefault() so the browser's default single-box paste never happens. The clipboard text is stripped to digits only, sliced to at most six characters, and distributed one digit per box in a single loop — then focus jumps to whichever box comes after the last pasted digit (or the last box, if all six were pasted).

A real, live countdown on the resend link

startCountdown() resets a 30-second counter, disables the Resend button, and runs a setInterval that decrements the displayed number every second, re-enabling the button and swapping its label back to plain "Resend code" once the counter reaches zero. Clicking Resend while enabled clears every box, refocuses the first one, and restarts the same countdown — so a user can't resend indefinitely without waiting out the timer each time.

Step by step

How to Use

  1. 1
    Load the snippetClick "Two-Factor OTP Verification Screen" in the sidebar to load its HTML, CSS, and JS into the editor panels. The preview updates instantly.
  2. 2
    Edit the codeModify any panel — HTML, CSS, or JS. The preview refreshes as you type. Use Reset in each panel header to restore the original.
  3. 3
    Preview on devicesClick the Mobile (375px), Tablet (768px), or Desktop buttons in the preview header to check responsiveness.
  4. 4
    Export in your formatClick "HTML" to download a standalone file, "JSX" for a React component, "Tailwind" for a React + Tailwind CSS component, "Tailwind HTML" for a standalone HTML file with Tailwind CDN, "Vue" for a Vue 3 SFC with <template>/<script setup>/<style scoped>, or "Angular" for a standalone Angular .component.ts file. "Copy all" copies the full code to clipboard.
  5. 5
    Save your versionClick "Save as", type a name, and press Enter. Your snippet saves to IndexedDB and appears in the Saved tab.

Real-world uses

Common Use Cases

Two-factor authentication flows
The standard verification step after password login for 2FA-protected accounts.
Phone or email verification
Confirm ownership of a phone number or email address during signup.
Passwordless / magic-code login
Use as the core input for login flows built entirely around one-time codes.
Teaching input-focus management
A clear example of coordinating focus across multiple inputs based on typing and paste events.

Got questions?

Frequently Asked Questions

Each box listens for its own input event. After stripping non-digit characters, if the box now holds a digit and it is not the last box, JavaScript calls .focus() on the next box in the boxes array.

The paste event is intercepted with preventDefault(), the clipboard text is reduced to digits only and capped at 6 characters, then each digit is written into the corresponding box by index in a single loop, after which focus moves to the box after the last one filled.

startCountdown() sets a 30-second counter, disables the Resend button, and runs a setInterval that decrements and redisplays the number every second. When it reaches zero, the interval is cleared, the button re-enables, and its label reverts to plain "Resend code".

The six box values are joined together; if the resulting string is shorter than 6 characters, an inline error message is shown and the verify action does not proceed.