GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 ID Verification Screen UI Patterns, Paste-Ready

An id verification screen ui is a screen — a permission prompt, an OTP grid, a security question card — that proves a person is who they say they are before an

Auto-plays · click a tile to jump to its section · all nine in one zip

The order below follows the path a real account actually takes, not a popularity list. It opens with the permission prompt an app shows before it can even check anything, moves through the checks that prove a human is present (captcha, the otp input ui design, a security question), then covers the two ways a person gets back into a locked account (recovery picker, onboarding after verification), and closes with the screens that manage an already-verified session (linked accounts, a timeout warning, and signing out). Each demo keeps a single screen centered on its own page so you can lift out one piece at a time. The gate just before this path is 9 Login Form Design Patterns, and the settings screens that come after verification are 9 account security settings UI patterns.

01Permission request modal

What you see on an identity verification screen

When the app asks for camera or location access, the icon bounces gently in place, and the allow button carries a solid fill next to deny's plain outline so the safer default stands out.

Permission request modal
Open in a new tab

01 · Permission request modal — Measured area 26.15% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

@keyframes 아이콘 바운스포커스 트랩aria-modal

Where to look in the code

It's the screen right before an identity check itself, used for camera or mic prompts, or a first-launch notification permission screen.

(function(){
  var scene = document.querySelector('.perm-scene');
  var modal = scene.querySelector('.perm-scene__modal');
  var deny = scene.querySelector('.perm-scene__btn--ghost');
  var allow = scene.querySelector('.perm-scene__btn--primary');
  function close(){ scene.classList.remove('is-demo'); scene.classList.add('is-closed'); }
  deny.addEventListener('click', close);
  allow.addEventListener('click', close);
  modal.addEventListener('keydown', function(e){
    if (e.key === 'Escape') close();
  });
}());

02Captcha verify checkbox

What happens on screen

Clicking the box spins a brief loader, then swaps to a green check while a verified badge pops in with a springy overshoot.

Captcha verify checkbox
Open in a new tab

02 · Captcha verify checkbox — Measured area 13.285% · moving frames 14/23Original by GODRICH · SCSS and React ship in the zip · See the password

@keyframes spin → scalerotate(560deg) 스피너:checked 상태

How to read the code

It's the human-check step right before an OTP goes out, and it doubles as spam prevention on a free-trial signup form.

.captcha__input:checked ~ .captcha__box .captcha__check {
  opacity: 1;
  transform: scale(1);
  transition: opacity $dur-quick $ease-out, transform $dur-quick $ease-pop;
}
.captcha__input:checked ~ .captcha__box { border-color: $color; }
.captcha__input:focus-visible ~ .captcha__box { outline: 2px solid $color; outline-offset: 2px; }

@keyframes capt-check {
  0%, 28%   { opacity: 0; transform: scale(.4); }
  40%       { opacity: 1; transform: scale(1.15); }
  46%, 84%  { opacity: 1; transform: scale(1); }
  92%, 100% { opacity: 0; transform: scale(.4); }
}

03OTP code input grid

What moves

Six digit boxes sit in a row; typing in one auto-advances to the next while its border flashes blue for a beat.

OTP code input grid
Open in a new tab

03 · OTP code input grid — Measured area 15.469% · moving frames 12/23Original by GODRICH · SCSS and React ship in the zip · See the password

input 이벤트 자동 이동paste 이벤트 6자리 분배inputmode=numeric

What to change

It's built for a 6-digit SMS code, the same layout that shows up on almost every two-factor authentication screen.

boxes.forEach(function(box, i){
  box.addEventListener('input', function(){
    box.value = box.value.replace(/[^0-9]/g, '').slice(0, 1);
    if (box.value && boxes[i + 1]) boxes[i + 1].focus({ preventScroll: true });
  });
  box.addEventListener('keydown', function(e){
    if (e.key === 'Backspace' && !box.value && boxes[i - 1]) boxes[i - 1].focus({ preventScroll: true });
  });
  box.addEventListener('paste', function(e){
    e.preventDefault();
    var digits = (e.clipboardData.getData('text') || '').replace(/[^0-9]/g, '').split('');
    boxes.forEach(function(b, j){ b.value = digits[j] || ''; });
    (boxes[Math.min(digits.length, boxes.length) - 1] || boxes[0]).focus({ preventScroll: true });
  });
});

04Security question card

What you see

A pre-registered question sits on the card while a red warning glow pulses around it and the answer field, cooling to plain white and a muted gray between pulses.

Security question card
Open in a new tab

04 · Security question card — Measured area 26.226% · moving frames 7/23Original by GODRICH · SCSS and React ship in the zip · See the password

@keyframes sq-panelbox-shadow 글로우aria-label

Where to look in the code

It's the fallback screen for when an OTP can't be delivered, and it also covers a secondary check on an admin account.

.sq-card__input:focus-visible { border-color: $subject-blue; box-shadow: 0 0 0 3px rgba($subject-blue, .25); }

@keyframes sq-panel {
  0%, 8%    { background: mix($color, white, 24%); box-shadow: 0 0 0 3px rgba($color, .3), 0 0 34px 18px rgba($color, .3), $shadow-raised; }
  23%, 77%  { background: $stage-paper; box-shadow: $shadow-raised; }
  92%, 100% { background: mix($color, white, 24%); box-shadow: 0 0 0 3px rgba($color, .3), 0 0 34px 18px rgba($color, .3), $shadow-raised; }
}
@keyframes sq-input {
  0%, 8%    { border-color: $color; box-shadow: 0 0 0 4px rgba($color, .22), 0 0 20px 10px rgba($color, .25); }
  23%, 77%  { border-color: #d8d2c4; box-shadow: none; }
  92%, 100% { border-color: $color; box-shadow: 0 0 0 4px rgba($color, .22), 0 0 20px 10px rgba($color, .25); }
}

05Recovery method picker

What happens on screen

Email, text, and security-question options sit as radio cards, and picking one lifts just that card into a bright highlight on the dark backdrop.

Recovery method picker
Open in a new tab

05 · Recovery method picker — Measured area 23.378% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

role=radiogroup:has(:checked)@keyframes picker-cycle

How to read the code

It's the first screen a person sees when choosing a recovery channel after forgetting a password, and it's also a check before unlocking a locked account.

.recovery__card:has(.recovery__input:checked) {
  background: mix($color, $stage-ink, 26%);
  border-color: $color;
  box-shadow: 0 0 0 3px rgba($color, .3), 0 0 24px 12px rgba($color, .28);
  transform: scale(1.04);
}
.recovery__card:has(.recovery__input:focus-visible) { outline: 2px solid $color; outline-offset: 2px; }

.recovery.is-demo .recovery__card { animation: picker-cycle $duration $easing infinite; }
.recovery.is-demo .recovery__card:nth-child(1) { animation-delay: 0ms; }
.recovery.is-demo .recovery__card:nth-child(2) { animation-delay: 660ms; }
.recovery.is-demo .recovery__card:nth-child(3) { animation-delay: 1320ms; }

06Onboarding wizard step card

What moves

Right after verification, a profile-setup card slides its content to the left when you tap next, and the following pastel-toned card slides in to replace it.

Onboarding wizard step card
Open in a new tab

06 · Onboarding wizard step card — Measured area 20.839% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · See the password

transform: translateX 슬라이드커스텀 프로퍼티 --step@keyframes wiz-track

What to change

It's the profile-setup step that follows signup verification, and the same pattern collects information across a first-run onboarding flow.

(function(){
  var track = document.querySelector('.wizard__track');
  var dots = Array.prototype.slice.call(document.querySelectorAll('.wizard__dot'));
  var btn = document.querySelector('.wizard__next');
  var step = 0;
  btn.addEventListener('click', function(){
    step = step === 0 ? 1 : 0;
    track.style.transform = 'translateX(' + (step === 0 ? '0%' : '-50%') + ')';
    dots.forEach(function(d, i){ d.classList.toggle('is-active', i === step); });
    btn.textContent = step === 0 ? 'Next' : 'Back';
  });
}());

07Linked accounts list

What you see

Login providers like Google or GitHub sit in rows, and flipping the connect switch slides the toggle over while the row tints with a soft background color.

Linked accounts list
Open in a new tab

07 · Linked accounts list — Measured area 12.77% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password

checkbox 기반 토글transform: translateXfocus-visible 링

Where to look in the code

It's the social-login management screen in account settings, the one dashboard that unifies every sign-in method a person has linked.

.accounts__input:checked ~ .accounts__track { background: $color; }
.accounts__input:checked ~ .accounts__track .accounts__knob { transform: translateX(14px); }
.accounts__input:focus-visible ~ .accounts__track { outline: 2px solid $color; outline-offset: 2px; }
.accounts__row:has(.accounts__input:checked) { background: rgba($subject-mint, .18); }

@keyframes acct-knob-cycle {
  0%, 8%    { transform: translateX(0); }
  14%, 36%  { transform: translateX(14px); }
  42%, 100% { transform: translateX(0); }
}

08Session timeout warning banner

What happens on screen

A banner slides down from the top and counts down the remaining seconds, and tapping extend resets the timer back to 30 without dismissing the banner.

Session timeout warning banner
Open in a new tab

08 · Session timeout warning banner — Measured area 14.106% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password

transform: translateY 슬라이드setInterval 카운트다운role=alert

How to read the code

It's the auto-logout warning on a banking or payment app, and the same countdown covers an idle-session alert in an admin console.

(function(){
  var cnt = document.querySelector('.timeout-banner__count'),
      btn = document.querySelector('.timeout-banner__extend'),
      n = 30,
      timer = setInterval(function(){
        n--; if (n < 0) n = 0; cnt.textContent = n;
      }, 1000);
  btn.addEventListener('click', function(){
    n = 30; cnt.textContent = n;
  });
}());

09Sign-out confirmation card

What moves

The card shakes and washes red in a recurring warning loop, and hovering the final leave button deepens its background to a darker red.

Sign-out confirmation card
Open in a new tab

09 · Sign-out confirmation card — Measured area 28.216% · moving frames 7/23Original by GODRICH · SCSS and React ship in the zip · See the password

@keyframes shake커스텀 프로퍼티 --dangerfocus-visible 링

What to change

It's a double-check against an accidental sign-out tap, and it's also the screen that warns before signing out every device at once.

(function(){
  var card = document.querySelector('.signout-card'),
      cancel = card.querySelector('.signout-card__btn--cancel'),
      danger = card.querySelector('.signout-card__btn--danger');
  cancel.addEventListener('click', function(){ card.classList.remove('is-confirmed'); });
  danger.addEventListener('click', function(){ card.classList.add('is-confirmed'); });
}());

Where it breaks — the trap

The real trap while building these nine showed up in item 03, the OTP code grid. Calling a plain .focus() on the auto-advance step makes the browser scroll the newly focused element into view by default, and at a 320px mobile width, where the input sits inside a scroll container, every auto-advance caused a tiny scroll jump even though the next box was already fully visible. The fix was adding { preventScroll: true } to every .focus() call in the grid, as shown in the code above, and MDN's focus() preventScroll documentation confirms this is exactly what the option is for — skipping the browser's default scroll-into-view behavior. It's now a standing rule for this component going forward: even though only the OTP grid calls .focus() among these nine demos, it does it three times, and all three now carry preventScroll: true. Filling a screen that has nothing on it yet, while verification is pending, is covered by 9 Empty State UI Design Cards. The zip behind the download password zkv9xjxt carries every one of these calls already fixed, so pasting the code doesn't reintroduce the jump.

Accessibility (reduced-motion)

All nine turn off their decorative loops under prefers-reduced-motion: reduce — the permission modal's icon bounce, the captcha badge's spring overshoot, the OTP border flash, the security card's glow cycle, the recovery picker's auto-cycle highlight, the wizard's slide transition, the linked-accounts knob demo loop, and the sign-out card's shake — while every real state change still works instantly on click. The permission modal (01) is marked as a modal dialog and closes on Escape, the captcha checkbox (02) and toggle switches (07) expose their checked state to screen readers through the native checkbox itself, the timeout banner (08) announces its countdown through role="alert" so a screen reader hears it without scanning, and the wizard (06) keeps its progress visible with active-state dots on each step, though the step change itself isn't separately announced to screen readers.

These patterns pair well with the login and signup cards a person sees just before reaching any of these verification screens. More on what this site ships is on the about page.

FAQ

Does this 2fa screen ui include real ID verification or OTP logic?

No. These are screen UI and animation patterns only — the OTP grid accepts and formats digits, but nothing here calls a real SMS provider or checks a code against a server. Wiring up actual verification, session, or identity logic is up to your own backend. Digging into the six-box grid alone, across nine states, is OTP Input UI.

Can I use just one of these nine instead of the whole set?

Yes. Each demo centers a single screen on its own page, and the zip ships that one screen's HTML, SCSS, and JS separately, so dropping in just the OTP grid or just the timeout banner works fine on its own.

Does the React version handle the OTP paste and countdown the same way?

Partially. The countdown timer runs through useEffect and useState in the React version and ticks the same way as the vanilla timer, but the OTP paste handler there works through useState and refs directly rather than an effect, and neither component takes duration, easing, or color as props — for those, the vanilla SCSS demo tokens are still the source of truth.

Enter the archive password

The password is inside this article. You will find it as you read.