webdevpuneet.com

Magic Link Login — Passwordless Email Sign-In

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

CategoryForms

Copy & paste

Source Code

<div class="mll-card">
  <div class="mll-icon">
    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><path d="M22 6l-10 7L2 6"/></svg>
  </div>

  <div id="mllForm">
    <h3>Sign in</h3>
    <p>Enter your email and we'll send you a link — no password to remember.</p>
    <input type="email" id="mllEmail" placeholder="[email protected]" autocomplete="email">
    <p class="mll-error" id="mllError" hidden></p>
    <button type="button" class="mll-submit" id="mllSubmit">
      <span id="mllSubmitLabel">Send magic link</span>
    </button>
  </div>

  <div id="mllSent" hidden>
    <h3>Check your email</h3>
    <p>We sent a sign-in link to <strong id="mllSentEmail"></strong>. Click it on this device to continue.</p>
    <button type="button" class="mll-resend" id="mllResend">Resend link</button>
    <button type="button" class="mll-back" id="mllBack">Use a different email</button>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

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 [email protected] 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.

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.
Related: Return Label Generator
See the Return Label Generator for a related forms pattern worth pairing with this one.

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.