More Forms Snippets
Magic Link Login — Passwordless Email Sign-In
Magic Link Login · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Magic Link Login — Passwordless Sign-In, Resend Cooldown & Check-Your-Email State

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
- 1Paste HTML, CSS, and JSA "Sign in" card renders with an email input and a "Send magic link" button.
- 2Submit an invalid or empty emailAn inline error message appears below the field without sending anything.
- 3Submit a valid emailThe button shows "Sending…", then the card swaps to a "Check your email" confirmation showing the address you entered.
- 4Try resendingThe "Resend link" button is disabled with a live "Resend link in 30s" countdown immediately after sending.
- 5Wait out the cooldownOnce it reaches 0, the resend button re-enables and can be clicked to restart the cooldown.
- 6Connect 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
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.
Magic Link Login — Export as HTML, React, Vue, Angular & Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Magic Link Login</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.mll-card{background:#fff;border-radius:18px;padding:28px;width:100%;max-width:360px;box-shadow:0 18px 44px rgba(15,23,42,.12);text-align:center}
.mll-icon{width:52px;height:52px;border-radius:14px;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;margin:0 auto 16px}
.mll-card h3{font-size:17px;font-weight:800;color:#0f172a;margin-bottom:7px}
.mll-card p{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:16px}
.mll-card p strong{color:#1e293b}
#mllForm input{width:100%;border:1.5px solid #e2e8f0;border-radius:10px;padding:11px 13px;font-size:14px;font-family:inherit;color:#0f172a;margin-bottom:8px;text-align:left;transition:border-color .15s,box-shadow .15s}
#mllForm input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.mll-error{color:#dc2626;font-size:12px;font-weight:600;text-align:left;margin-bottom:8px !important}
.mll-submit{width:100%;background:#6366f1;color:#fff;border:none;border-radius:10px;padding:12px;font-size:14px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:8px}
.mll-submit:hover{background:#4f46e5}
.mll-submit:disabled{opacity:.7;cursor:default}
.mll-resend{width:100%;background:transparent;border:1.5px solid #e2e8f0;border-radius:10px;padding:10px;font-size:13.5px;font-weight:700;color:#1e293b;cursor:pointer;margin-bottom:8px;transition:background .15s}
.mll-resend:hover:not(:disabled){background:#f8fafc}
.mll-resend:disabled{color:#cbd5e1;cursor:default}
.mll-back{background:transparent;border:none;color:#94a3b8;font-size:12.5px;font-weight:600;cursor:pointer;padding:6px}
.mll-back:hover{color:#64748b}
</style>
</head>
<body>
<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>
<script>
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var resendCooldown = 0;
var cooldownTimer = null;
function isValidEmail(v) { return EMAIL_RE.test(v.trim()); }
document.getElementById('mllSubmit').addEventListener('click', function () {
var input = document.getElementById('mllEmail');
var error = document.getElementById('mllError');
var email = input.value.trim();
if (!email) { error.textContent = 'Enter your email address.'; error.hidden = false; return; }
if (!isValidEmail(email)) { error.textContent = 'That email address looks invalid.'; error.hidden = false; return; }
error.hidden = true;
var btn = this, label = document.getElementById('mllSubmitLabel');
btn.disabled = true; label.textContent = 'Sending…';
setTimeout(function () {
document.getElementById('mllSentEmail').textContent = email;
document.getElementById('mllForm').hidden = true;
document.getElementById('mllSent').hidden = false;
btn.disabled = false; label.textContent = 'Send magic link';
startCooldown();
}, 900);
});
function startCooldown() {
resendCooldown = 30;
var resend = document.getElementById('mllResend');
resend.disabled = true;
updateResendLabel();
clearInterval(cooldownTimer);
cooldownTimer = setInterval(function () {
resendCooldown--;
updateResendLabel();
if (resendCooldown <= 0) {
clearInterval(cooldownTimer);
resend.disabled = false;
resend.textContent = 'Resend link';
}
}, 1000);
}
function updateResendLabel() {
var resend = document.getElementById('mllResend');
resend.textContent = resendCooldown > 0 ? 'Resend link in ' + resendCooldown + 's' : 'Resend link';
}
document.getElementById('mllResend').addEventListener('click', function () {
if (this.disabled) return;
this.textContent = 'Sending…';
var self = this;
setTimeout(function () { startCooldown(); }, 600);
});
document.getElementById('mllBack').addEventListener('click', function () {
clearInterval(cooldownTimer);
document.getElementById('mllSent').hidden = true;
document.getElementById('mllForm').hidden = false;
document.getElementById('mllEmail').value = '';
document.getElementById('mllEmail').focus();
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Magic Link Login</title>
<!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
}
.mll-card h3 {
font-size:17px;font-weight:800;color:#0f172a;margin-bottom:7px
}
.mll-card p {
font-size:13px;color:#64748b;line-height:1.55;margin-bottom:16px
}
.mll-card p strong {
color:#1e293b
}
#mllForm input {
width:100%;border:1.5px solid #e2e8f0;border-radius:10px;padding:11px 13px;font-size:14px;font-family:inherit;color:#0f172a;margin-bottom:8px;text-align:left;transition:border-color .15s,box-shadow .15s
}
#mllForm input:focus {
outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)
}
.mll-submit:disabled {
opacity:.7;cursor:default
}
.mll-resend:hover:not(:disabled) {
background:#f8fafc
}
.mll-resend:disabled {
color:#cbd5e1;cursor:default
}
</style>
</head>
<body>
<div class="mll-card bg-[#fff] rounded-[18px] p-7 w-full max-w-[360px] shadow-[0_18px_44px_rgba(15,23,42,.12)] text-center">
<div class="w-[52px] h-[52px] rounded-[14px] [background:linear-gradient(135deg,#6366f1,#8b5cf6)] flex items-center justify-center mt-0 mx-auto mb-4">
<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="text-[#dc2626] text-xs font-semibold text-left mb-[8px !important]" id="mllError" hidden></p>
<button type="button" class="mll-submit w-full bg-[#6366f1] text-[#fff] border-0 rounded-[10px] p-3 text-sm font-bold cursor-pointer [transition:background_.15s,opacity_.15s] flex items-center justify-center gap-2 hover:bg-[#4f46e5]" 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 w-full bg-transparent border rounded-[10px] p-2.5 text-[13.5px] font-bold text-[#1e293b] cursor-pointer mb-2 [transition:background_.15s]" id="mllResend">Resend link</button>
<button type="button" class="bg-transparent border-0 text-[#94a3b8] text-[12.5px] font-semibold cursor-pointer p-1.5 hover:text-[#64748b]" id="mllBack">Use a different email</button>
</div>
</div>
<script>
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var resendCooldown = 0;
var cooldownTimer = null;
function isValidEmail(v) { return EMAIL_RE.test(v.trim()); }
document.getElementById('mllSubmit').addEventListener('click', function () {
var input = document.getElementById('mllEmail');
var error = document.getElementById('mllError');
var email = input.value.trim();
if (!email) { error.textContent = 'Enter your email address.'; error.hidden = false; return; }
if (!isValidEmail(email)) { error.textContent = 'That email address looks invalid.'; error.hidden = false; return; }
error.hidden = true;
var btn = this, label = document.getElementById('mllSubmitLabel');
btn.disabled = true; label.textContent = 'Sending…';
setTimeout(function () {
document.getElementById('mllSentEmail').textContent = email;
document.getElementById('mllForm').hidden = true;
document.getElementById('mllSent').hidden = false;
btn.disabled = false; label.textContent = 'Send magic link';
startCooldown();
}, 900);
});
function startCooldown() {
resendCooldown = 30;
var resend = document.getElementById('mllResend');
resend.disabled = true;
updateResendLabel();
clearInterval(cooldownTimer);
cooldownTimer = setInterval(function () {
resendCooldown--;
updateResendLabel();
if (resendCooldown <= 0) {
clearInterval(cooldownTimer);
resend.disabled = false;
resend.textContent = 'Resend link';
}
}, 1000);
}
function updateResendLabel() {
var resend = document.getElementById('mllResend');
resend.textContent = resendCooldown > 0 ? 'Resend link in ' + resendCooldown + 's' : 'Resend link';
}
document.getElementById('mllResend').addEventListener('click', function () {
if (this.disabled) return;
this.textContent = 'Sending…';
var self = this;
setTimeout(function () { startCooldown(); }, 600);
});
document.getElementById('mllBack').addEventListener('click', function () {
clearInterval(cooldownTimer);
document.getElementById('mllSent').hidden = true;
document.getElementById('mllForm').hidden = false;
document.getElementById('mllEmail').value = '';
document.getElementById('mllEmail').focus();
});
</script>
</body>
</html>import React, { useEffect } from 'react';
// CSS — optionally move to MagicLinkLogin.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.mll-card{background:#fff;border-radius:18px;padding:28px;width:100%;max-width:360px;box-shadow:0 18px 44px rgba(15,23,42,.12);text-align:center}
.mll-icon{width:52px;height:52px;border-radius:14px;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;margin:0 auto 16px}
.mll-card h3{font-size:17px;font-weight:800;color:#0f172a;margin-bottom:7px}
.mll-card p{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:16px}
.mll-card p strong{color:#1e293b}
#mllForm input{width:100%;border:1.5px solid #e2e8f0;border-radius:10px;padding:11px 13px;font-size:14px;font-family:inherit;color:#0f172a;margin-bottom:8px;text-align:left;transition:border-color .15s,box-shadow .15s}
#mllForm input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.mll-error{color:#dc2626;font-size:12px;font-weight:600;text-align:left;margin-bottom:8px !important}
.mll-submit{width:100%;background:#6366f1;color:#fff;border:none;border-radius:10px;padding:12px;font-size:14px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:8px}
.mll-submit:hover{background:#4f46e5}
.mll-submit:disabled{opacity:.7;cursor:default}
.mll-resend{width:100%;background:transparent;border:1.5px solid #e2e8f0;border-radius:10px;padding:10px;font-size:13.5px;font-weight:700;color:#1e293b;cursor:pointer;margin-bottom:8px;transition:background .15s}
.mll-resend:hover:not(:disabled){background:#f8fafc}
.mll-resend:disabled{color:#cbd5e1;cursor:default}
.mll-back{background:transparent;border:none;color:#94a3b8;font-size:12.5px;font-weight:600;cursor:pointer;padding:6px}
.mll-back:hover{color:#64748b}
`;
export default function MagicLinkLogin() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var resendCooldown = 0;
var cooldownTimer = null;
function isValidEmail(v) { return EMAIL_RE.test(v.trim()); }
document.getElementById('mllSubmit').addEventListener('click', function () {
var input = document.getElementById('mllEmail');
var error = document.getElementById('mllError');
var email = input.value.trim();
if (!email) { error.textContent = 'Enter your email address.'; error.hidden = false; return; }
if (!isValidEmail(email)) { error.textContent = 'That email address looks invalid.'; error.hidden = false; return; }
error.hidden = true;
var btn = this, label = document.getElementById('mllSubmitLabel');
btn.disabled = true; label.textContent = 'Sending…';
setTimeout(function () {
document.getElementById('mllSentEmail').textContent = email;
document.getElementById('mllForm').hidden = true;
document.getElementById('mllSent').hidden = false;
btn.disabled = false; label.textContent = 'Send magic link';
startCooldown();
}, 900);
});
function startCooldown() {
resendCooldown = 30;
var resend = document.getElementById('mllResend');
resend.disabled = true;
updateResendLabel();
clearInterval(cooldownTimer);
cooldownTimer = setInterval(function () {
resendCooldown--;
updateResendLabel();
if (resendCooldown <= 0) {
clearInterval(cooldownTimer);
resend.disabled = false;
resend.textContent = 'Resend link';
}
}, 1000);
}
function updateResendLabel() {
var resend = document.getElementById('mllResend');
resend.textContent = resendCooldown > 0 ? 'Resend link in ' + resendCooldown + 's' : 'Resend link';
}
document.getElementById('mllResend').addEventListener('click', function () {
if (this.disabled) return;
this.textContent = 'Sending…';
var self = this;
setTimeout(function () { startCooldown(); }, 600);
});
document.getElementById('mllBack').addEventListener('click', function () {
clearInterval(cooldownTimer);
document.getElementById('mllSent').hidden = true;
document.getElementById('mllForm').hidden = false;
document.getElementById('mllEmail').value = '';
document.getElementById('mllEmail').focus();
});
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{css}</style>
<div className="mll-card">
<div className="mll-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="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 className="mll-error" id="mllError" hidden></p>
<button type="button" className="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" className="mll-resend" id="mllResend">Resend link</button>
<button type="button" className="mll-back" id="mllBack">Use a different email</button>
</div>
</div>
</>
);
}import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+
export default function MagicLinkLogin() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var resendCooldown = 0;
var cooldownTimer = null;
function isValidEmail(v) { return EMAIL_RE.test(v.trim()); }
document.getElementById('mllSubmit').addEventListener('click', function () {
var input = document.getElementById('mllEmail');
var error = document.getElementById('mllError');
var email = input.value.trim();
if (!email) { error.textContent = 'Enter your email address.'; error.hidden = false; return; }
if (!isValidEmail(email)) { error.textContent = 'That email address looks invalid.'; error.hidden = false; return; }
error.hidden = true;
var btn = this, label = document.getElementById('mllSubmitLabel');
btn.disabled = true; label.textContent = 'Sending…';
setTimeout(function () {
document.getElementById('mllSentEmail').textContent = email;
document.getElementById('mllForm').hidden = true;
document.getElementById('mllSent').hidden = false;
btn.disabled = false; label.textContent = 'Send magic link';
startCooldown();
}, 900);
});
function startCooldown() {
resendCooldown = 30;
var resend = document.getElementById('mllResend');
resend.disabled = true;
updateResendLabel();
clearInterval(cooldownTimer);
cooldownTimer = setInterval(function () {
resendCooldown--;
updateResendLabel();
if (resendCooldown <= 0) {
clearInterval(cooldownTimer);
resend.disabled = false;
resend.textContent = 'Resend link';
}
}, 1000);
}
function updateResendLabel() {
var resend = document.getElementById('mllResend');
resend.textContent = resendCooldown > 0 ? 'Resend link in ' + resendCooldown + 's' : 'Resend link';
}
document.getElementById('mllResend').addEventListener('click', function () {
if (this.disabled) return;
this.textContent = 'Sending…';
var self = this;
setTimeout(function () { startCooldown(); }, 600);
});
document.getElementById('mllBack').addEventListener('click', function () {
clearInterval(cooldownTimer);
document.getElementById('mllSent').hidden = true;
document.getElementById('mllForm').hidden = false;
document.getElementById('mllEmail').value = '';
document.getElementById('mllEmail').focus();
});
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{`
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
}
.mll-card h3 {
font-size:17px;font-weight:800;color:#0f172a;margin-bottom:7px
}
.mll-card p {
font-size:13px;color:#64748b;line-height:1.55;margin-bottom:16px
}
.mll-card p strong {
color:#1e293b
}
#mllForm input {
width:100%;border:1.5px solid #e2e8f0;border-radius:10px;padding:11px 13px;font-size:14px;font-family:inherit;color:#0f172a;margin-bottom:8px;text-align:left;transition:border-color .15s,box-shadow .15s
}
#mllForm input:focus {
outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)
}
.mll-submit:disabled {
opacity:.7;cursor:default
}
.mll-resend:hover:not(:disabled) {
background:#f8fafc
}
.mll-resend:disabled {
color:#cbd5e1;cursor:default
}
`}</style>
<div className="mll-card bg-[#fff] rounded-[18px] p-7 w-full max-w-[360px] shadow-[0_18px_44px_rgba(15,23,42,.12)] text-center">
<div className="w-[52px] h-[52px] rounded-[14px] [background:linear-gradient(135deg,#6366f1,#8b5cf6)] flex items-center justify-center mt-0 mx-auto mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="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 className="text-[#dc2626] text-xs font-semibold text-left mb-[8px !important]" id="mllError" hidden></p>
<button type="button" className="mll-submit w-full bg-[#6366f1] text-[#fff] border-0 rounded-[10px] p-3 text-sm font-bold cursor-pointer [transition:background_.15s,opacity_.15s] flex items-center justify-center gap-2 hover:bg-[#4f46e5]" 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" className="mll-resend w-full bg-transparent border rounded-[10px] p-2.5 text-[13.5px] font-bold text-[#1e293b] cursor-pointer mb-2 [transition:background_.15s]" id="mllResend">Resend link</button>
<button type="button" className="bg-transparent border-0 text-[#94a3b8] text-[12.5px] font-semibold cursor-pointer p-1.5 hover:text-[#64748b]" id="mllBack">Use a different email</button>
</div>
</div>
</>
);
}<template>
<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>
</template>
<script setup>
import { onMounted } from 'vue';
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var resendCooldown = 0;
var cooldownTimer = null;
function isValidEmail(v) { return EMAIL_RE.test(v.trim()); }
function startCooldown() {
resendCooldown = 30;
var resend = document.getElementById('mllResend');
resend.disabled = true;
updateResendLabel();
clearInterval(cooldownTimer);
cooldownTimer = setInterval(function () {
resendCooldown--;
updateResendLabel();
if (resendCooldown <= 0) {
clearInterval(cooldownTimer);
resend.disabled = false;
resend.textContent = 'Resend link';
}
}, 1000);
}
function updateResendLabel() {
var resend = document.getElementById('mllResend');
resend.textContent = resendCooldown > 0 ? 'Resend link in ' + resendCooldown + 's' : 'Resend link';
}
onMounted(() => {
document.getElementById('mllSubmit').addEventListener('click', function () {
var input = document.getElementById('mllEmail');
var error = document.getElementById('mllError');
var email = input.value.trim();
if (!email) { error.textContent = 'Enter your email address.'; error.hidden = false; return; }
if (!isValidEmail(email)) { error.textContent = 'That email address looks invalid.'; error.hidden = false; return; }
error.hidden = true;
var btn = this, label = document.getElementById('mllSubmitLabel');
btn.disabled = true; label.textContent = 'Sending…';
setTimeout(function () {
document.getElementById('mllSentEmail').textContent = email;
document.getElementById('mllForm').hidden = true;
document.getElementById('mllSent').hidden = false;
btn.disabled = false; label.textContent = 'Send magic link';
startCooldown();
}, 900);
});
document.getElementById('mllResend').addEventListener('click', function () {
if (this.disabled) return;
this.textContent = 'Sending…';
var self = this;
setTimeout(function () { startCooldown(); }, 600);
});
document.getElementById('mllBack').addEventListener('click', function () {
clearInterval(cooldownTimer);
document.getElementById('mllSent').hidden = true;
document.getElementById('mllForm').hidden = false;
document.getElementById('mllEmail').value = '';
document.getElementById('mllEmail').focus();
});
});
</script>
<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.mll-card{background:#fff;border-radius:18px;padding:28px;width:100%;max-width:360px;box-shadow:0 18px 44px rgba(15,23,42,.12);text-align:center}
.mll-icon{width:52px;height:52px;border-radius:14px;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;margin:0 auto 16px}
.mll-card h3{font-size:17px;font-weight:800;color:#0f172a;margin-bottom:7px}
.mll-card p{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:16px}
.mll-card p strong{color:#1e293b}
#mllForm input{width:100%;border:1.5px solid #e2e8f0;border-radius:10px;padding:11px 13px;font-size:14px;font-family:inherit;color:#0f172a;margin-bottom:8px;text-align:left;transition:border-color .15s,box-shadow .15s}
#mllForm input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.mll-error{color:#dc2626;font-size:12px;font-weight:600;text-align:left;margin-bottom:8px !important}
.mll-submit{width:100%;background:#6366f1;color:#fff;border:none;border-radius:10px;padding:12px;font-size:14px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:8px}
.mll-submit:hover{background:#4f46e5}
.mll-submit:disabled{opacity:.7;cursor:default}
.mll-resend{width:100%;background:transparent;border:1.5px solid #e2e8f0;border-radius:10px;padding:10px;font-size:13.5px;font-weight:700;color:#1e293b;cursor:pointer;margin-bottom:8px;transition:background .15s}
.mll-resend:hover:not(:disabled){background:#f8fafc}
.mll-resend:disabled{color:#cbd5e1;cursor:default}
.mll-back{background:transparent;border:none;color:#94a3b8;font-size:12.5px;font-weight:600;cursor:pointer;padding:6px}
.mll-back:hover{color:#64748b}
</style>// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-magic-link-login',
standalone: true,
imports: [CommonModule],
encapsulation: ViewEncapsulation.None,
template: `
<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>
`,
styles: [`
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.mll-card{background:#fff;border-radius:18px;padding:28px;width:100%;max-width:360px;box-shadow:0 18px 44px rgba(15,23,42,.12);text-align:center}
.mll-icon{width:52px;height:52px;border-radius:14px;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;margin:0 auto 16px}
.mll-card h3{font-size:17px;font-weight:800;color:#0f172a;margin-bottom:7px}
.mll-card p{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:16px}
.mll-card p strong{color:#1e293b}
#mllForm input{width:100%;border:1.5px solid #e2e8f0;border-radius:10px;padding:11px 13px;font-size:14px;font-family:inherit;color:#0f172a;margin-bottom:8px;text-align:left;transition:border-color .15s,box-shadow .15s}
#mllForm input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.mll-error{color:#dc2626;font-size:12px;font-weight:600;text-align:left;margin-bottom:8px !important}
.mll-submit{width:100%;background:#6366f1;color:#fff;border:none;border-radius:10px;padding:12px;font-size:14px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:8px}
.mll-submit:hover{background:#4f46e5}
.mll-submit:disabled{opacity:.7;cursor:default}
.mll-resend{width:100%;background:transparent;border:1.5px solid #e2e8f0;border-radius:10px;padding:10px;font-size:13.5px;font-weight:700;color:#1e293b;cursor:pointer;margin-bottom:8px;transition:background .15s}
.mll-resend:hover:not(:disabled){background:#f8fafc}
.mll-resend:disabled{color:#cbd5e1;cursor:default}
.mll-back{background:transparent;border:none;color:#94a3b8;font-size:12.5px;font-weight:600;cursor:pointer;padding:6px}
.mll-back:hover{color:#64748b}
`]
})
export class MagicLinkLoginComponent implements AfterViewInit {
ngAfterViewInit(): void {
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var resendCooldown = 0;
var cooldownTimer = null;
function isValidEmail(v) { return EMAIL_RE.test(v.trim()); }
document.getElementById('mllSubmit').addEventListener('click', function () {
var input = document.getElementById('mllEmail');
var error = document.getElementById('mllError');
var email = input.value.trim();
if (!email) { error.textContent = 'Enter your email address.'; error.hidden = false; return; }
if (!isValidEmail(email)) { error.textContent = 'That email address looks invalid.'; error.hidden = false; return; }
error.hidden = true;
var btn = this, label = document.getElementById('mllSubmitLabel');
btn.disabled = true; label.textContent = 'Sending…';
setTimeout(function () {
document.getElementById('mllSentEmail').textContent = email;
document.getElementById('mllForm').hidden = true;
document.getElementById('mllSent').hidden = false;
btn.disabled = false; label.textContent = 'Send magic link';
startCooldown();
}, 900);
});
function startCooldown() {
resendCooldown = 30;
var resend = document.getElementById('mllResend');
resend.disabled = true;
updateResendLabel();
clearInterval(cooldownTimer);
cooldownTimer = setInterval(function () {
resendCooldown--;
updateResendLabel();
if (resendCooldown <= 0) {
clearInterval(cooldownTimer);
resend.disabled = false;
resend.textContent = 'Resend link';
}
}, 1000);
}
function updateResendLabel() {
var resend = document.getElementById('mllResend');
resend.textContent = resendCooldown > 0 ? 'Resend link in ' + resendCooldown + 's' : 'Resend link';
}
document.getElementById('mllResend').addEventListener('click', function () {
if (this.disabled) return;
this.textContent = 'Sending…';
var self = this;
setTimeout(function () { startCooldown(); }, 600);
});
document.getElementById('mllBack').addEventListener('click', function () {
clearInterval(cooldownTimer);
document.getElementById('mllSent').hidden = true;
document.getElementById('mllForm').hidden = false;
document.getElementById('mllEmail').value = '';
document.getElementById('mllEmail').focus();
});
// Bind inline-handler functions to the component so the template can call them
Object.assign(this, { isValidEmail, startCooldown, updateResendLabel });
}
}