DHxWhy

QR Code Scan Login Form with Password Fallback

Sign-in 113

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.3K
  • Sushi Conveyor Belt Menu – Restaurant Hero Section

    Sushi Conveyor Belt Menu – Restaurant Hero Section

    370
  • Sun Moon Dark Mode Theme Toggle

    Sun Moon Dark Mode Theme Toggle

    281
  • Team Lanyard Rail – Meet the Team Section with Flip Badges

    Team Lanyard Rail – Meet the Team Section with Flip Badges

    223
  • Live Notification Feed

    Live Notification Feed

    151
  • Skateboard Deck Rail Gallery – Skate Shop Hero Section

    Skateboard Deck Rail Gallery – Skate Shop Hero Section

    150
  • Country Flags Globe

    Country Flags Globe

    134
  • Airplane Window Gallery

    Airplane Window Gallery

    124

More in Sign-in

View all
  • Modern & Stunning sign in

    Modern & Stunning sign in

    1.6K
  • SignInPage

    SignInPage

    675
  • Clean & Minimal sign in

    Clean & Minimal sign in

    633
  • Sign In

    Sign In

    99
  • Sign In Form

    Sign In Form

    208
  • Sign In Card

    Sign In Card

    18
  • Split Sign In Form

    Split Sign In Form

    58
  • Centered Social Login

    Centered Social Login

    54