Magic Link Login — Passwordless Email Sign-In

Magic Link Login · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Two-state card, no page navigation
The email form and check-your-email confirmation share one container, toggled via hidden — no route change needed.
Practical email validation
A pragmatic regex catches the common invalid shapes without claiming full RFC email compliance.
Distinct empty vs. invalid error messages
Users get a specific reason for the error rather than one generic "invalid input" message.
Real resend cooldown
A live 30-second countdown disables the resend button, preventing accidental or abusive repeat sends.
Simulated async submit state
The submit button disables and relabels to "Sending…" during the request, reading as a real network call.
"Use a different email" reset
Going back clears the countdown timer and refocuses the input, avoiding stale background timers.
No password field anywhere
The entire flow is built around the passwordless pattern from the first screen, not a password field with a "forgot password" afterthought.
Clean, focused single-purpose card
No unrelated UI competing for attention — the card does exactly one job, the way a real auth screen should.

About this UI Snippet

Magic Link Login — Passwordless Sign-In, Resend Cooldown & Check-Your-Email State

Screenshot of the Magic Link Login snippet rendered live

Passwordless sign-in removes the single biggest source of authentication friction — a forgotten password — by sending a one-time sign-in link to a verified email instead. This snippet builds the complete client-side flow: email validation, a simulated send, a "check your email" confirmation screen, and a resend button gated by a real countdown so it can't be spammed.

Two screens, one card

The component is really two states sharing one container: the email-entry form and the check-your-email confirmation, toggled via hidden. Rather than navigating to a new page, swapping the inner content keeps the user anchored to the same card, which matters for a flow whose entire job is to get the user comfortable waiting for an email — a page navigation here would feel like the flow had "moved on" without them.

Validation before the simulated send

A simple regex (/^[^\s@]+@[^\s@]+\.[^\s@]+$/) checks for a plausible email shape — not full RFC 5322 compliance, which is famously a rabbit hole no client-side regex fully solves, but the basic local-part@domain.tld shape that catches the vast majority of real typos. An empty field and an invalid shape get distinct messages, and the submit button disables with a "Sending…" label during the simulated 900ms delay, so the interaction reads as a real network round trip.

A resend button with teeth

The "Resend link" button isn't just decorative — clicking the original submit (or resend) starts a real 30-second countdown via setInterval, during which the resend button is disabled and shows "Resend link in 27s", ticking down live. This is a meaningful anti-abuse pattern: without it, a button that silently fires another email on every click is an easy way to accidentally (or deliberately) spam an inbox or a rate-limited API.

Going back without losing context

"Use a different email" clears the countdown timer, swaps back to the entry form, clears the input, and refocuses it — so correcting a typo'd email doesn't require a page reload and doesn't leave a stale countdown silently running in the background after the user has already navigated away from that state.

Why this beats a traditional password field

A password field comes with its own failure modes — weak passwords, reused passwords, forgotten passwords, and the support burden of a reset flow that itself usually emails a link anyway. Magic links collapse all of that into the one flow most users already trust: check email, click link, you're in. The tradeoff is a dependency on email deliverability, which is why the resend control and a clear "check your spam folder" expectation matter as much as the visual design.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to reconstruct the two-screen-one-card structure by inspecting the markup cold. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain precisely how toggling the hidden attribute on mllForm and mllSent swaps state without a page navigation, and why startCooldown() clears the previous interval with clearInterval before starting a new one. The same assistant can help optimize it, for instance asking whether the 30-second resend cooldown should also be enforced server-side so reloading the page cannot bypass the client-side timer entirely. It is also useful for extending the flow: ask it to wire the submit handler to a real auth provider's magic-link endpoint with proper error states, add a fallback password or OTP option for users whose email is slow to arrive, or persist the pending email across a page refresh using sessionStorage. 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 "magic link login" flow in plain HTML, CSS, and JavaScript with no libraries, using a single card that toggles between two states rather than navigating to a new page.

Requirements:
- One card element containing two child sections, an email-entry form and a check-your-email confirmation, where exactly one is visible at a time via the hidden attribute (no separate route or page load involved in switching between them).
- Submitting the form must validate the email with a pragmatic regex (rejecting empty input and input without an @ and a domain with a dot) and show a specific, distinct error message for "empty" versus "invalid shape" — not one generic error string.
- On valid submission, disable the submit button and change its label to a sending state, then after a simulated delay reveal the confirmation section showing the exact email address that was submitted, and hide the form section.
- Revealing the confirmation section must start a real visible countdown (not just a disabled flag): a resend button that is disabled and shows the exact remaining seconds counting down once per second via setInterval, re-enabling itself and reverting its label only once the countdown reaches zero.
- Clicking resend while the countdown is at zero must restart the same cooldown sequence, and any previously running interval must be explicitly cleared before starting a new one so multiple intervals never stack.
- A "use a different email" control must clear the running cooldown interval, switch back to the form section, clear the email input's value, and return keyboard focus to that input.

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 "Sign in" card renders with an email input and a "Send magic link" button.
  2. 2
    Submit an invalid or empty emailAn inline error message appears below the field without sending anything.
  3. 3
    Submit a valid emailThe button shows "Sending…", then the card swaps to a "Check your email" confirmation showing the address you entered.
  4. 4
    Try resendingThe "Resend link" button is disabled with a live "Resend link in 30s" countdown immediately after sending.
  5. 5
    Wait out the cooldownOnce it reaches 0, the resend button re-enables and can be clicked to restart the cooldown.
  6. 6
    Connect a real email-sending APIReplace the setTimeout in the submit handler with a fetch call to your auth provider's magic-link endpoint, keeping the same UI state transitions.

Real-world uses

Common Use Cases

SaaS and B2B app sign-in
The increasingly standard sign-in pattern for tools like Slack, Notion, and Linear — pair with an OTP input if you also support a code-based fallback.
Newsletter and content gating
Let readers sign in to save articles or comments without ever creating a password.
Low-friction onboarding
Reduce sign-up abandonment by removing the password field from your very first user-facing screen.
Internal tools and admin panels
A quick, password-free sign-in for trusted internal staff using their company email.
Mobile-first web apps
Avoid the password-typing friction that's especially painful on a phone keyboard.
Learning multi-state card patterns
A clear example of toggling between form and confirmation states in one container — compare with a multi-step form for a longer flow.

Got questions?

Frequently Asked Questions

Replace the setTimeout in the submit handler with a fetch POST to your auth provider (Supabase, Auth0, Firebase, or your own backend) passing the validated email; show the check-your-email state only after that request resolves successfully, and show the existing error element if it fails.

The magic link itself points to a callback route in your app containing a one-time token; on that page, verify the token server-side, create a session, and redirect to your authenticated area — this snippet only covers the request side of the flow, not the callback handler.

Mirror the client-side 30-second cooldown with a server-side check (e.g. a timestamp stored per email/IP) so the cooldown can't be bypassed by simply reloading the page and resubmitting, since client-side timers alone are not a security control.

Add a "Sign in with a password instead" link beneath the form that swaps to a password field, or combine with an OTP input for a code-based alternative if email delivery is unreliable for some users.

In React, track the email, error, sent, and cooldown values in useState and run the countdown with setInterval inside useEffect (clearing it on unmount); in Vue, use ref()/onUnmounted for the same cleanup; in Angular, use a component field with ngOnDestroy. The validation regex and state transitions port directly.