DHxWhy

QR Code Scan Login Form with Password Fallback

Sign-in 110

About

QR Code Scan Login Form with Password Fallback for React and Tailwind CSS (shadcn/ui) pairs QR scanning with email sign-in. It is built to help people move from phone to desktop. Includes scan motion, confirmation, expiry, refresh, and a sliding password form; controlled status and callbacks; keyboard access, reduced motion, and light/dark themes. Use it for SaaS and app logins. The included demo simulates sign-in; the component makes no requests. Requires React and lucide-react, with no animation library.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds QR Code Scan Login Form with Password Fallback to your project and fits it to your theme.

Code

How QR Code Scan Login Form with Password Fallback is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import * as React from "react";
import { Pause, Play, RotateCcw } from "lucide-react";
import { QrCodeScanLogin } from "@/components/ui/qr-code-scan-login";
import type { QrCodeLoginStatus } from "@/components/ui/qr-code-scan-login";

// Generated with qrcode 1.5.4. Payload: https://example.com/demo (no authentication).
// Regenerate with review/original/variants/qr-code-scan-login/generate-qr.cjs.
export const DEMO_QR_MODULES: boolean[][] = /* QR_MODULES_START */ [
  [true,true,true,true,true,true,true,false,false,false,true,true,true,true,false,false,true,false,true,true,true,true,true,true,true],
  [true,false,false,false,false,false,true,false,false,true,true,true,true,true,true,true,true,false,true,false,false,false,false,false,true],
  [true,false,true,true,true,false,true,false,true,false,false,false,true,false,true,false,false,false,true,false,true,true,true,false,true],
  [true,false,true,true,true,false,true,false,true,true,false,false,true,true,true,true,false,false,true,false,true,true,true,false,true],
  [true,false,true,true,true,false,true,false,true,false,true,true,false,true,false,false,true,false,true,false,true,true,true,false,true],
  [true,false,false,false,false,false,true,false,true,false,true,false,true,false,true,true,false,false,true,false,false,false,false,false,true],
  [true,true,true,true,true,true,true,false,true,false,true,false,true,false,true,false,true,false,true,true,true,true,true,true,true],
  [false,false,false,false,false,false,false,false,true,true,false,false,true,false,true,false,true,false,false,false,false,false,false,false,false],
  [true,false,true,true,true,true,true,false,false,false,true,false,false,true,false,false,false,false,true,true,true,true,true,false,false],
  [false,true,true,false,false,true,false,true,true,true,false,true,false,true,false,false,false,true,false,true,false,false,false,true,false],
  [true,false,true,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false,false,true,false,true,false,true,true],
  [true,true,false,true,false,true,false,false,false,false,true,false,true,false,true,true,true,false,true,true,false,false,false,false,true],
  [true,true,false,false,false,true,true,true,true,false,true,true,false,true,true,true,false,true,true,false,true,false,true,true,true],
  [true,true,true,true,true,true,false,false,true,false,true,false,true,false,false,false,true,false,false,true,false,true,false,true,false],
  [true,false,true,false,false,false,true,true,true,false,true,false,false,false,true,true,false,false,true,true,true,true,false,true,true],
  [true,false,false,true,false,true,false,true,true,true,true,false,true,false,false,true,true,true,true,true,true,false,false,false,true],
  [true,false,true,true,true,false,true,true,true,true,true,false,true,true,true,false,true,true,true,true,true,false,true,false,false],
  [false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,true,true,false,false,false,true,true,false,false,false],
  [true,true,true,true,true,true,true,false,false,false,false,true,true,false,true,false,true,false,true,false,true,false,true,true,true],
  [true,false,false,false,false,false,true,false,true,false,false,true,false,false,true,false,true,false,false,false,true,true,false,false,true],
  [true,false,true,true,true,false,true,false,true,true,false,true,true,true,true,true,true,true,true,true,true,false,true,false,false],
  [true,false,true,true,true,false,true,false,true,true,false,false,true,true,false,true,false,true,true,false,true,true,true,true,true],
  [true,false,true,true,true,false,true,false,true,true,false,false,false,false,true,true,false,false,false,false,false,true,true,false,true],
  [true,false,false,false,false,false,true,false,false,true,false,true,false,false,true,false,true,true,false,true,true,true,false,false,true],
  [true,true,true,true,true,true,true,false,true,false,false,true,true,true,true,false,false,true,true,true,true,true,true,true,true]
] /* QR_MODULES_END */;

type DemoProps = { autoplay?: boolean };

export default function QrCodeScanLoginDemo({ autoplay = true }: DemoProps = {}) {
  const [status, setStatus] = React.useState("waiting" as QrCodeLoginStatus);
  const [passwordOpen, setPasswordOpen] = React.useState(false);
  const [paused, setPaused] = React.useState(false);
  const [reduced, setReduced] = React.useState(true);
  const [ready, setReady] = React.useState(false);
  const [cycle, setCycle] = React.useState(0);
  const [message, setMessage] = React.useState("");
  const playing = autoplay && ready && !reduced && !paused;
  const stop = () => setPaused(true);

  React.useLayoutEffect(() => {
    const media = window.matchMedia("(prefers-reduced-motion: reduce)");
    const update = () => setReduced(media.matches);
    update();
    setReady(true);
    media.addEventListener("change", update);
    return () => media.removeEventListener("change", update);
  }, []);

  React.useEffect(() => {
    if (!playing) return;
    const timers = [
      window.setTimeout(() => setStatus("scanned"), 2600),
      window.setTimeout(() => setStatus("confirmed"), 4400),
      window.setTimeout(() => { setStatus("waiting"); setPasswordOpen(true); }, 6000),
      window.setTimeout(() => { setStatus("waiting"); setPasswordOpen(false); setCycle((value) => value + 1); }, 7200),
    ];
    return () => timers.forEach((timer) => window.clearTimeout(timer));
  }, [playing, cycle]);

  const replay = () => {
    setStatus("waiting");
    setPasswordOpen(false);
    setMessage("");
    setCycle((value) => value + 1);
    setPaused(false);
  };

  return (
    <main data-qr-demo data-playing={playing} data-autoplay={autoplay ? "on" : "off"} className="flex min-h-svh w-full flex-col items-center justify-center gap-4 bg-muted/40 px-5 py-5 text-foreground">
      <div className="flex w-full max-w-[660px] items-center justify-between gap-3 px-1"><p className="text-xs font-medium tracking-tight">Your next session starts here.</p><span className="rounded-full border border-border px-2 py-1 text-[9px] font-medium uppercase tracking-[.16em] text-muted-foreground">Demo</span></div>
      <div className="w-full max-w-[660px]" onPointerDownCapture={stop} onKeyDownCapture={stop}>
        <QrCodeScanLogin
          qrModules={DEMO_QR_MODULES}
          appName="Wayline"
          logo={<svg viewBox="0 0 24 24" fill="none" className="size-6"><path d="m4 7 4 11 4-8 4 8 4-11" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" /></svg>}
          status={status}
          onStatusChange={setStatus}
          codeId={cycle}
          deviceName="iPhone"
          welcomeMessage="Welcome back, Maria"
          autoplay={playing}
          passwordOpen={passwordOpen}
          onPasswordOpenChange={setPasswordOpen}
          onUsePassword={stop}
          onRefresh={() => { setStatus("waiting"); setCycle((value) => value + 1); }}
          onPasswordSubmit={() => setMessage("Demo only · no sign-in request was sent")}
        />
      </div>
      <div className="flex w-full max-w-[660px] items-center justify-between gap-2 px-1 text-[10px] text-muted-foreground">
        <span data-demo-note role="status" className="min-w-0">{message || "Sample workspace · No real sign-in"}</span>
        <div className="flex shrink-0 items-center gap-3">
          {autoplay && !reduced && <button type="button" data-pause aria-label={playing ? "Pause demo" : "Play demo"} onClick={() => playing ? stop() : replay()} className="flex items-center gap-1 rounded p-1 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">{playing ? <Pause className="size-3" aria-hidden="true" /> : <Play className="size-3" aria-hidden="true" />}{playing ? "Pause" : "Play"}</button>}
          <button type="button" data-replay onClick={replay} className="flex items-center gap-1 rounded p-1 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"><RotateCcw className="size-3" aria-hidden="true" />Replay</button>
        </div>
      </div>
    </main>
  );
}

Dependencies

More by DHxWhy

View all
  • Product Rail Gallery – Fashion Store Hero Section

    Product Rail Gallery – Fashion Store Hero Section

    1.1K
  • Sun Moon Dark Mode Theme Toggle

    Sun Moon Dark Mode Theme Toggle

    256
  • Live Notification Feed

    Live Notification Feed

    143
  • Country Flags Globe

    Country Flags Globe

    128
  • Airplane Window Gallery

    Airplane Window Gallery

    117
  • Domino Scroll Image Gallery

    Domino Scroll Image Gallery

    109
  • Ferris Wheel Image Gallery

    Ferris Wheel Image Gallery

    102
  • Hotel Elevator Doors Image Gallery

    Hotel Elevator Doors Image Gallery

    93

More in Sign-in

View all
  • Modern & Stunning sign in

    Modern & Stunning sign in

    1.6K
  • SignInPage

    SignInPage

    667
  • Sign In Form

    Sign In Form

    206
  • Clean & Minimal sign in

    Clean & Minimal sign in

    631
  • Sign In

    Sign In

    98
  • Sign In Card

    Sign In Card

    18
  • Split Sign In Form

    Split Sign In Form

    46
  • Sign In Auth Card

    Sign In Auth Card

    3