Xzero UI
Pro library

Magic linkPro

A passwordless sign in card where an email with inline validation and typo fixes sends a magic link, and the card crossfades in place to a check your inbox step with a sealing envelope, Gmail and Outlook shortcuts and a resend countdown. Google and GitHub sign in sit above, with their real marks.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/magic-link
Live · keyboard ready

Drop <MagicLink /> on a sign in page or in a dialog. Connect onSendLink(email, signal) to your auth provider (Clerk, Supabase, Auth.js, Better Auth, WorkOS). It covers the first send and every resend. Throw from it to show the error state; when it resolves, the card moves to the inbox step. Connect onProviderSignIn(provider, signal) to the provider's social redirect. Set appName and sender, and use validateEmail to require a work domain. providers={[]} and mailApps={[]} hide the social buttons and the inbox shortcuts. resendAfter and expiresInMinutes set the countdown and the expiry copy. By default the inbox shortcuts open a Gmail search for the sender, or outlook.live.com / outlook.office.com depending on the address. Pass onOpenMail to change that.

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/magic-link

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { MagicLink } from "@/components/xzero/blocks/magic-link/magic-link";
import { authClient } from "@/lib/auth-client";

export function SignInPage() {
  return (
    <MagicLink
      appName="Halden"
      sender="login@halden.app"
      onSendLink={async (email, signal) => {
        const res = await fetch("/api/auth/magic-link", {
          method: "POST",
          body: JSON.stringify({ email, redirectTo: "/dashboard" }),
          signal,
        });
        if (!res.ok) throw new Error("Send failed");
      }}
      onProviderSignIn={provider => authClient.signIn.social({ provider, callbackURL: "/dashboard" })}
      validateEmail={email => /@(gmail|yahoo)\./i.test(email) ? "Use your work email" : null}
      termsHref="/terms"
      privacyHref="/privacy"
    />
  );
}

API reference

MagicLink

A passwordless sign in card. Google and GitHub buttons sit above an email field with inline validation and a typo check ("Did you mean maya@gmail.com?"). Send magic link shows a spinner, then the card crossfades in place to Check your inbox: an envelope seals the letter, the address and expiry are shown, Open Gmail and Open Outlook jump to the inbox, a resend countdown ticks in tabular numerals and Use a different email goes back with the field focused. Both steps share one grid cell and one footer hairline, so the card never changes size. With no props every send is simulated.

PropTypeDefaultDescription
appNamestring"Halden"Your product's name, used in the title ("Sign in to Halden") and the card's accessible name.
senderstring"login@halden.app"The address the link comes from. Shown on the inbox step and used by Open Gmail to search for the email.
defaultEmailstring""Prefills the email field, for example from a previous session or an invite.
emailLabelstring"Work email"The label above the email field.
providers("google" | "github")[]["google", "github"]Social sign in buttons, in order, with their real marks. One provider spans the row. Pass [] to hide the buttons and the divider.
mailApps("gmail" | "outlook")[]["gmail", "outlook"]Inbox shortcuts on the Check your inbox step. Pass [] to hide them.
resendAfternumber30Seconds before the link can be sent again. The countdown restarts after every resend.
expiresInMinutesnumber15How long the link stays valid. Display only; enforce it on the server.
onSendLink(email: string, signal: AbortSignal) => Promise<void> | void–Sends the magic link, for the first send and every resend. Throw to show "We couldn't send the link" (or Retry resend). The signal aborts when the person goes back or the card unmounts. Leave it out to simulate a send; nothing is sent and a status message says so.
onProviderSignIn(provider: "google" | "github", signal: AbortSignal) => Promise<void> | void–Starts a social sign in, usually a redirect to your auth provider. The button shows a spinner until it settles; throw to show an inline error. Leave it out to simulate one.
onOpenMail(app: "gmail" | "outlook", email: string) => void–Handles the inbox shortcuts. By default Gmail opens a search for the sender in a new tab, and Outlook opens outlook.live.com for personal addresses or outlook.office.com for work ones.
validateEmail(email: string) => string | null | undefined–Extra validation after the format check, such as allowing only company domains. Return the error message to show, or nothing to accept.
termsHrefstring"#"Link to your terms in the footer.
privacyHrefstring"#"Link to your privacy policy in the footer.
classNamestring–Class on the root wrapper, which spans its container and centers the card.

Keyboard interactions

KeysAction
TabMoves through Google, GitHub, the email field, Send magic link and the footer links; on the inbox step through the inbox shortcuts, Use a different email and Resend.
EnterSubmits the email field. On a typo hint, pressing Enter again sends to the address as typed.
Space / EnterActivates the focused button, including the suggested address in a typo hint.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Guidance for AI tools

Blocks are complete, self-contained screens with sample data. Replace the sample data and connect the callbacks described above. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components