OTP Verification Screen — 2FA Code Input UI

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

Share & Support

What's included

Features

Auto-advancing digit boxes
Typing a digit moves focus forward and Backspace moves it back, so entering the code feels like typing one number.
Paste-to-fill
Pasting a 6-digit code into any box distributes it across all six and submits if complete.
Auto-submit on completion
verify() fires the moment the last digit lands, removing the separate "click Verify" step on the happy path.
One-time-code autofill
autocomplete="one-time-code" and inputmode="numeric" surface the SMS code and number pad on mobile.
Error shake and retry
A wrong code shakes the boxes, shows an error, then clears and refocuses the first box automatically.
Green success state
A correct code turns the boxes green, shows a verified status, and locks the inputs.
Resend cooldown timer
A live 30-second countdown gates the resend button, preventing request spam like a real backend.
Full keyboard navigation
Arrow keys, Backspace, and digit entry all move focus correctly across the six boxes.

About this UI Snippet

OTP Verification Screen — Auto-Advancing Code Boxes with Resend Timer

Screenshot of the OTP Verification Screen snippet rendered live

Entering a one-time passcode is a high-friction moment in any sign-in or sign-up flow — the user is switching between their inbox or SMS and your form, and every fumbled keystroke risks abandonment. A well-built OTP screen removes that friction: digits auto-advance, a pasted code fills every box at once, and the form submits itself the moment all six are entered. This snippet builds that complete verification screen in plain HTML, CSS, and vanilla JavaScript.

Auto-advance and backspace navigation

Each of the six boxes holds a single digit. Typing a number moves focus to the next box automatically, and pressing Backspace in an empty box jumps to the previous one — so the user never has to manually tab between fields. Arrow keys move left and right too. Non-numeric input is stripped on the fly (inputmode="numeric" also brings up the number pad on mobile), so the field only ever contains digits. This auto-advance behavior is what makes entering a six-digit code feel like typing one number rather than filling six separate inputs.

Paste the whole code at once

The single most important convenience: pasting "123456" (or a code copied from an email) into any box fills all six. The paste handler extracts the digits, distributes them across the inputs, focuses the next empty box, and — if the paste completed the code — submits immediately. Combined with the autocomplete="one-time-code" attribute (which lets iOS and Android offer the SMS code above the keyboard), this means a user can often verify in a single tap.

Auto-submit when complete

The moment the sixth digit lands — whether typed or pasted — verify() runs automatically, so there's no separate "now click Verify" step in the common case. The explicit Verify button remains for accessibility and as a fallback, but the happy path is hands-off. This is the behavior users expect from modern auth flows and it measurably reduces drop-off at the verification step.

Clear success and error feedback

A correct code turns the boxes green with a "Verified!" status and locks the inputs. An incorrect code shakes the boxes (a short translateX keyframe), shows an error message, then clears and refocuses the first box so the user can retry without manually deleting six digits. The shake is a well-established pattern for "wrong input, try again" that reads instantly without needing to read the text.

Resend with a cooldown

A "Resend code" button sits below, gated by a live 30-second countdown ("Resend in 27s") so a user can't spam resend requests — the same anti-abuse pattern a real auth backend enforces server-side. Resending clears the boxes, resets the state, and restarts the timer. This handles the genuinely common case where the first code never arrives, without letting it become a way to hammer your SMS/email provider.

Production notes

The demo checks against a hardcoded code; a real build replaces that with a fetch to your verification endpoint and shows the success or error state from its response. The verification must always happen server-side — a client-side comparison is purely for the demo. The component exposes the entered code and the verify hook cleanly, so wiring it to Auth0, Firebase, Supabase, or your own backend is a one-function change.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace every branch of this state machine by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the code decides to call verify automatically once code().length hits six, including from both typing and pasting, or how startCooldown's setInterval keeps the resend button's countdown text and disabled state in sync without drifting. The same assistant can help optimize it too, for instance asking whether clearInterval is being called reliably enough to avoid stacking multiple timers if resend gets clicked in quick succession. It is also a fast way to extend the flow: ask it to add a visible countdown ring around the verify button, support voice-read-back of the entered digits for accessibility, or swap the hardcoded CORRECT check for a real fetch call with proper loading and network-error states. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a 6-digit "OTP verification screen" in plain HTML, CSS, and JavaScript, with no framework and no input-mask library.

Requirements:
- Six single-character input boxes, each with inputmode numeric, and the first one carrying autocomplete one-time-code so mobile browsers can offer to autofill an SMS code.
- Typing a digit must strip non-numeric characters, advance focus to the next box, and check after every keystroke whether all six boxes are now filled — if so, call the verification function automatically without waiting for a button click.
- Support ArrowLeft and ArrowRight to move focus between boxes, and Backspace on an empty box to move focus to the previous box.
- A paste handler must intercept the paste event, extract only digits from the clipboard text, distribute them across the six boxes starting from the first, focus the next empty box (or the last box if all are filled), and trigger auto-verification if the pasted value completes all six digits.
- On successful verification, all six boxes must switch to a success visual style and become disabled, and a success message must appear; on failure, the boxes must play a shake animation, show an error message, then after a short delay automatically clear all six boxes and refocus the first one.
- Include a "Resend code" button that starts disabled with a live countdown (e.g. "Resend in 30s" ticking down once per second via setInterval), re-enabling itself and resetting its label only once the countdown reaches zero, and clicking it must clear the boxes, reset any error/success state, and restart the countdown.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA verification screen renders with six code boxes; the first is focused and a resend countdown starts.
  2. 2
    Type the codeEnter digits — focus auto-advances, and when all six are filled the form verifies automatically (try 123456).
  3. 3
    Or paste itPaste a 6-digit code into any box — all six fill at once and it auto-submits.
  4. 4
    See error handlingEnter a wrong code — the boxes shake red, show an error, then clear and refocus for a retry.
  5. 5
    Resend after the cooldownWait out the 30-second timer, then click "Resend code" to reset and get a new code.
  6. 6
    Connect your auth backendReplace the hardcoded CORRECT check in verify() with a fetch to your server's verification endpoint.

Real-world uses

Common Use Cases

Two-factor authentication
The code-entry step after password sign-in — pair with a password requirements checklist on the signup side.
Email and phone verification
Confirm a new account's email or phone with a sent code during onboarding.
Passwordless login
Enter an emailed login code, complementing a magic link login as the code-based alternative.
Transaction and payment confirmation
Verify a sensitive action (transfer, purchase) with a one-time code.
Device and session authorization
Approve a new device sign-in with a code shown on a trusted device.
Learning multi-input UX
A reference for auto-advance, paste distribution, and auto-submit — compare with an OTP input for the bare input and a pin pad for keypad entry.

Got questions?

Frequently Asked Questions

Replace the hardcoded CORRECT comparison in verify() with a fetch POST to your verification endpoint, sending the entered code; show the success state on a 200 and the error state on a rejection. Verification must happen server-side — a client-side check is only for the demo, since anyone can read the expected code in the page source.

The paste handler intercepts the clipboard text, strips non-digits, takes the first six, and assigns one to each input in order, then focuses the next empty box. If the pasted value completes the code it auto-submits. This works no matter which box receives the paste, so the user can paste anywhere.

The autocomplete="one-time-code" attribute on the first input lets iOS and Android suggest the most recent SMS code above the keyboard for one-tap entry, and inputmode="numeric" brings up the number pad. For the autofill to match, the SMS should follow the platform's format (including your domain on iOS), which is configured in how you send the message, not the markup.

Once all six digits are entered there's no ambiguity about intent, so submitting automatically removes a redundant step and measurably cuts drop-off at the verification stage. The explicit Verify button stays as an accessible fallback and for users who paste a partial code, but the common path is hands-off.

In React, hold an array of six digit values in useState and refs for focus management, advancing focus in the onChange handler and verifying when full; in Vue, use a reactive array with template refs; in Angular, use a FormArray and ViewChildren. The auto-advance, paste, and cooldown logic port directly — only focus management uses each framework's ref mechanism.