figma guide

Designing email verification and OTP UI in Figma: codes, links, states, and handoff

Design email verification and OTP UI in Figma with 6-digit codes, magic links, resend timers, expired states, and Dev Mode specs for signup and checkout flows.

Published
Updated
Jul 04, 2026
Read time
7 min
Level
Beginner

Quick answer

Email verification confirms that a user owns the address they typed—usually right after sign-up, password reset, or a sensitive account change. Design two common patterns: a 6-digit OTP input (one box per digit or a single field) and a “check your inbox” screen with a magic link. Include loading, success, invalid code, expired code, and resend-with-cooldown states. Keep copy short: show the masked email, explain what to do, and offer “Resend code” only after a visible timer. Start from the Figma guides hub and pair with login and registration, guest checkout, and account dashboard guides.


Who this is for

  • Product designers building sign-up, password reset, or email-change flows that require proof of inbox access.
  • Design system teams standardizing OTP inputs across web, mobile web, and native apps.
  • Engineers implementing rate limits, code expiry, autofill, and redirect-after-verify behavior.

Verification pattern comparison

PatternBest forUX trade-off
6-digit OTP inputMobile apps, fast in-app completionUser must switch apps or copy code
Magic link (email only)Web sign-up, low typing frictionDepends on same device/browser
OTP + magic link fallbackHigh-security or global audiencesMore copy to explain both paths
SMS OTPPhone-first authNot email—use separate component
Authenticator app (TOTP)MFA after loginDifferent UI—see login MFA section

Verdict: for ecommerce account creation, default to OTP input on a dedicated verify screen with optional “Open email app” helper on mobile. Offer magic link as a secondary path when your backend supports it.


VerifyEmailScreen anatomy

PartSpecNotes
IconEnvelope or shieldReinforces “check email”
Headline”Verify your email”Not “Confirm identity”
Subcopy”We sent a 6-digit code to j•••@example.com”Mask email for privacy
OTP input6 cells or single fieldSee OTP component below
Primary CTA”Verify” (if not auto-submit)Disabled until 6 digits
Resend row”Didn’t get it? Resend code” + timerDisabled for 30–60s
Change emailText linkReturns to registration
Help link”Check spam” tip inlineReduces support tickets
VerifyEmailScreen
├── Variant: state=idle | verifying | success | invalid | expired
├── Variant: channel=otp | magicLinkPending
├── Property: maskedEmail=j•••@example.com
└── Layers:
    ├── Icon
    ├── Headline
    ├── Subcopy
    ├── OtpInput
    ├── ErrorMessage (conditional)
    ├── VerifyButton (optional if auto-submit)
    ├── ResendRow
    │   ├── ResendLink
    │   └── CooldownTimer
    ├── ChangeEmailLink
    └── SpamTip

Place after Create account submit—not as a modal over the form unless space is tight on mobile.


OtpInput component spec

ApproachLayoutHandoff notes
6 separate cellsgap=8, equal width boxesinputmode=numeric, paste fills all
Single fieldOne input, letter-spacingSimpler a11y, less “premium” feel
Grouped 3+3Two inputs of 3 digitsRare—harder to paste
OtpInput
├── Variant: layout=cells6 | singleField
├── Variant: state=empty | partial | complete | error | disabled
├── Property: length=6
└── Layers:
    ├── Cell (×6) OR SingleField
    └── HiddenError (aria-live)

Behavior to spec: auto-advance focus on digit entry; backspace moves to previous cell; paste distributes digits; auto-submit when complete (optional). Mark error state on the group, not only one cell.


State matrix

StateUIUser action
IdleEmpty OTP, resend disabled with timerEnter code
VerifyingSpinner on button or skeleton cellsWait
SuccessCheckmark + redirect messageAuto-redirect to dashboard or checkout
Invalid codeRed border + “Code doesn’t match”Re-enter or resend
Expired”Code expired” + prominent resendRequest new code
Rate limited”Too many attempts—try again in 15 min”Wait or contact support
Already verifiedInfo banner + continue CTASkip to destination

Document return URL behavior: after verify, land on ?redirect= from login or guest checkout handoff.


When the backend sends a link instead of a code:

ElementCopy example
Headline”Check your email”
Body”Click the link we sent to j•••@example.com to verify your account.”
Open email CTA”Open Gmail” / “Open Mail” (mobile deep link)
ResendSame cooldown pattern as OTP
Wrong email”Use a different email” link
MagicLinkPending
├── Variant: state=sent | opened | expired | success
└── Layers:
    ├── Illustration
    ├── Headline
    ├── BodyCopy
    ├── OpenEmailButton (mobile)
    ├── ResendRow
    └── ChangeEmailLink

Do not show an OTP field on this variant—pick one pattern per flow to avoid confusion.


Where verification appears in ecommerce flows

TriggerWhen to verifySkip verify when
New registrationImmediately after sign-upSocial OAuth with verified email from provider
Post-purchase accountAfter password set on thank-you pageGuest order email already used—verify on first login
Email changeBefore new email is active
Password resetBefore new password formUse separate “reset link sent” screen
High-risk actionRe-verify before payment method changeTrusted device policy

Align with forms patterns for inline email validation before sending codes.


Mobile and accessibility

RequirementSpec
Numeric keyboardinputmode=numeric on OTP cells
AutofillSupport autocomplete=one-time-code
Focus orderLeft-to-right through cells
Error announcementaria-live=polite on invalid/expired
TimerVisible countdown; resend enabled at 0:00
ContrastError border + text meet WCAG AA
Touch targetsCells min 44×44px

On iOS, one-time codes from SMS/email often appear in the QuickType bar—design empty state copy that still makes sense when autofill appears.


Handoff checklist

ItemDev Mode annotation
Code length6 digits (or document if 4/8)
Expirye.g. 10 minutes—show in expired copy
Resend cooldown30s or 60s—disable link until elapsed
Max attemptsLock after N failures—show rate-limit state
Redirectredirect query param preserved through verify
Analyticsverify_email_sent, verify_email_success, verify_email_resend
i18nRTL: OTP order may reverse—confirm with eng
SecurityDo not log full codes in client analytics

Pair with Dev Mode handoff for token specs on error colors and focus rings.


Common mistakes

MistakeWhy it hurtsFix
No masked email in copyUsers unsure which inbox to checkShow j•••@domain.com
Resend available immediatelyAbuse + deliverability issuesCooldown timer on resend
OTP and magic link on same screenConfusing dual instructionsOne pattern per variant
Tiny OTP cells on mobileMis-taps, frustration44px min height
No expired stateUsers retry dead codes foreverClear expired + resend
Blocking checkout entirelyAbandonmentAllow browse; gate payment until verified if required
Generic “invalid” errorNo path forwardDistinguish wrong vs expired vs rate limited
Missing “change email”Stuck on typo addressesLink back to registration

  1. Choose pattern (OTP vs magic link) per platform and backend capability.
  2. Build OtpInput with cell and single-field variants plus error state.
  3. Design VerifyEmailScreen with idle, verifying, success, invalid, expired.
  4. Add resend row with cooldown timer component shared across auth flows.
  5. Map triggers in registration, post-purchase account, and email-change flows.
  6. Spec redirect, rate limits, and analytics for engineering.
  7. Prototype paste, autofill, and resend on mobile frame (375px).
  8. Cross-link login UI and account dashboard.

FAQ

Auto-submit when 6 digits are entered?

Yes on mobile when validation is instant—keep a manual Verify button on desktop if your team prefers explicit submit.

Verify before or after payment for new accounts?

After payment for guest-first stores; before first login for account-required catalogs. Match your guest checkout policy.

Same OTP component for MFA login?

Reuse OtpInput visuals but use a different screen title (“Enter sign-in code”) and shorter expiry copy—link from the login MFA variant.

Show code in email mockups in Figma?

Use placeholder 123456 in dev specs only—never use real codes in shared files.


Next steps

Share on X

§ Keep reading

Related guides.