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
| Pattern | Best for | UX trade-off |
|---|---|---|
| 6-digit OTP input | Mobile apps, fast in-app completion | User must switch apps or copy code |
| Magic link (email only) | Web sign-up, low typing friction | Depends on same device/browser |
| OTP + magic link fallback | High-security or global audiences | More copy to explain both paths |
| SMS OTP | Phone-first auth | Not email—use separate component |
| Authenticator app (TOTP) | MFA after login | Different 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
| Part | Spec | Notes |
|---|---|---|
| Icon | Envelope or shield | Reinforces “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 input | 6 cells or single field | See OTP component below |
| Primary CTA | ”Verify” (if not auto-submit) | Disabled until 6 digits |
| Resend row | ”Didn’t get it? Resend code” + timer | Disabled for 30–60s |
| Change email | Text link | Returns to registration |
| Help link | ”Check spam” tip inline | Reduces 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
| Approach | Layout | Handoff notes |
|---|---|---|
| 6 separate cells | gap=8, equal width boxes | inputmode=numeric, paste fills all |
| Single field | One input, letter-spacing | Simpler a11y, less “premium” feel |
| Grouped 3+3 | Two inputs of 3 digits | Rare—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
| State | UI | User action |
|---|---|---|
| Idle | Empty OTP, resend disabled with timer | Enter code |
| Verifying | Spinner on button or skeleton cells | Wait |
| Success | Checkmark + redirect message | Auto-redirect to dashboard or checkout |
| Invalid code | Red border + “Code doesn’t match” | Re-enter or resend |
| Expired | ”Code expired” + prominent resend | Request new code |
| Rate limited | ”Too many attempts—try again in 15 min” | Wait or contact support |
| Already verified | Info banner + continue CTA | Skip to destination |
Document return URL behavior: after verify, land on ?redirect= from login or guest checkout handoff.
Magic link pending screen
When the backend sends a link instead of a code:
| Element | Copy 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) |
| Resend | Same 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
| Trigger | When to verify | Skip verify when |
|---|---|---|
| New registration | Immediately after sign-up | Social OAuth with verified email from provider |
| Post-purchase account | After password set on thank-you page | Guest order email already used—verify on first login |
| Email change | Before new email is active | — |
| Password reset | Before new password form | Use separate “reset link sent” screen |
| High-risk action | Re-verify before payment method change | Trusted device policy |
Align with forms patterns for inline email validation before sending codes.
Mobile and accessibility
| Requirement | Spec |
|---|---|
| Numeric keyboard | inputmode=numeric on OTP cells |
| Autofill | Support autocomplete=one-time-code |
| Focus order | Left-to-right through cells |
| Error announcement | aria-live=polite on invalid/expired |
| Timer | Visible countdown; resend enabled at 0:00 |
| Contrast | Error border + text meet WCAG AA |
| Touch targets | Cells 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
| Item | Dev Mode annotation |
|---|---|
| Code length | 6 digits (or document if 4/8) |
| Expiry | e.g. 10 minutes—show in expired copy |
| Resend cooldown | 30s or 60s—disable link until elapsed |
| Max attempts | Lock after N failures—show rate-limit state |
| Redirect | redirect query param preserved through verify |
| Analytics | verify_email_sent, verify_email_success, verify_email_resend |
| i18n | RTL: OTP order may reverse—confirm with eng |
| Security | Do not log full codes in client analytics |
Pair with Dev Mode handoff for token specs on error colors and focus rings.
Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| No masked email in copy | Users unsure which inbox to check | Show j•••@domain.com |
| Resend available immediately | Abuse + deliverability issues | Cooldown timer on resend |
| OTP and magic link on same screen | Confusing dual instructions | One pattern per variant |
| Tiny OTP cells on mobile | Mis-taps, frustration | 44px min height |
| No expired state | Users retry dead codes forever | Clear expired + resend |
| Blocking checkout entirely | Abandonment | Allow browse; gate payment until verified if required |
| Generic “invalid” error | No path forward | Distinguish wrong vs expired vs rate limited |
| Missing “change email” | Stuck on typo addresses | Link back to registration |
Recommended workflow
- Choose pattern (OTP vs magic link) per platform and backend capability.
- Build
OtpInputwith cell and single-field variants plus error state. - Design
VerifyEmailScreenwith idle, verifying, success, invalid, expired. - Add resend row with cooldown timer component shared across auth flows.
- Map triggers in registration, post-purchase account, and email-change flows.
- Spec redirect, rate limits, and analytics for engineering.
- Prototype paste, autofill, and resend on mobile frame (375px).
- 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
- Design login, registration, and password recovery UI in Figma — sign-up entry and MFA challenge
- Design guest checkout and account creation UI in Figma — post-purchase verify timing
- Design account dashboard and my account UI in Figma — post-verify destination
- Design forms in Figma: inputs, states, and handoff — email field validation
- Figma Dev Mode for designers: handoff checklist — OTP and error token specs
§ Keep reading