A sign-up form that explains each field in turn. You can type into each field while it's highlighted.
@keyframeui
registry/demos/spotlight-tour-form-demo.tsx
"use client";
import * as React from "react";
import { SpotlightTour, type SpotlightTourStep } from "@/components/ui/spotlight-tour";
// Each field stays typable while it's highlighted.
const steps: SpotlightTourStep[] = [
{
target: "[data-tour=signup-name]",
title: "Your name",
body: "This is how teammates see you in comments and mentions.",
side: "right",
padding: 6,
interactive: true,
},
{
target: "[data-tour=signup-email]",
title: "Use your work email",
body: "So teammates can find you, and you join your company's workspace automatically.",
side: "right",
padding: 6,
interactive: true,
},
{
target: "[data-tour=signup-password]",
title: "Pick a password",
body: "At least 12 characters. A short sentence is easier to remember than symbols.",
side: "right",
padding: 6,
interactive: true,
},
{
target: "[data-tour=signup-workspace]",
title: "Name your workspace",
body: "It becomes your address. You can change it later in settings.",
side: "right",
padding: 6,
interactive: true,
},
{
target: "[data-tour=signup-submit]",
title: "You're ready",
body: "Create the workspace, then invite your team on the next screen.",
side: "right",
padding: 6,
},
];
function strength(password: string) {
let score = 0;
if (password.length >= 8) score++;
if (password.length >= 12) score++;
if (/[A-Z]/.test(password) && /[a-z]/.test(password)) score++;
if (/[\d\s\W]/.test(password)) score++;
return password ? Math.max(score, 1) : 0;
}
const meter = ["", "bg-rose-500", "bg-amber-500", "bg-lime-500", "bg-emerald-500"];
const field =
"h-10 w-full rounded-lg bg-white px-3 text-sm text-zinc-900 ring-1 ring-zinc-900/10 transition-shadow outline-none placeholder:text-zinc-400 focus:ring-2 focus:ring-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 dark:ring-white/10 dark:focus:ring-zinc-100";
const label = "mb-1.5 block text-sm font-medium text-zinc-900 dark:text-zinc-100";
export function SpotlightTourFormDemo() {
const [open, setOpen] = React.useState(false);
const [password, setPassword] = React.useState("");
const [workspace, setWorkspace] = React.useState("");
const [visible, setVisible] = React.useState(false);
const [created, setCreated] = React.useState(false);
const score = strength(password);
React.useEffect(() => {
if (!created) return;
const timer = window.setTimeout(() => setCreated(false), 2200);
return () => window.clearTimeout(timer);
}, [created]);
return (
<div className="w-full max-w-sm rounded-2xl border border-zinc-200 bg-zinc-50 p-6 dark:border-zinc-800 dark:bg-zinc-900">
<div className="flex items-start justify-between gap-4">
<div>
<h2 className="text-lg font-semibold tracking-tight text-zinc-900 dark:text-zinc-50">Create your workspace</h2>
<p className="mt-1 text-sm text-zinc-500">Free for teams of up to 5.</p>
</div>
<button
type="button"
onClick={() => setOpen(true)}
className="mt-0.5 shrink-0 rounded-md text-sm font-medium text-zinc-500 underline decoration-zinc-300 underline-offset-4 transition-colors hover:text-zinc-900 hover:decoration-zinc-900 dark:decoration-zinc-700 dark:hover:text-zinc-50 dark:hover:decoration-zinc-50"
>
Guide me
</button>
</div>
<form
className="mt-6 space-y-4"
onSubmit={(event) => {
event.preventDefault();
setCreated(true);
}}
>
<div>
<label htmlFor="signup-name" className={label}>
Full name
</label>
<input id="signup-name" data-tour="signup-name" autoComplete="name" placeholder="Jordan Lee" className={field} />
</div>
<div>
<label htmlFor="signup-email" className={label}>
Work email
</label>
<input
id="signup-email"
data-tour="signup-email"
type="email"
autoComplete="email"
placeholder="jordan@company.com"
className={field}
/>
</div>
<div>
<label htmlFor="signup-password" className={label}>
Password
</label>
<div data-tour="signup-password" className="rounded-lg">
<div className="relative">
<input
id="signup-password"
type={visible ? "text" : "password"}
autoComplete="new-password"
value={password}
onChange={(event) => setPassword(event.target.value)}
className={`${field} pr-16`}
/>
<button
type="button"
onClick={() => setVisible((on) => !on)}
className="absolute top-1/2 right-2 -translate-y-1/2 rounded px-1.5 py-0.5 text-xs font-medium text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-50"
>
{visible ? "Hide" : "Show"}
</button>
</div>
<div aria-hidden className="mt-2 grid grid-cols-4 gap-1">
{[1, 2, 3, 4].map((bar) => (
<span
key={bar}
className={`h-1 rounded-full transition-colors duration-300 ${bar <= score ? meter[score] : "bg-zinc-200 dark:bg-zinc-800"}`}
/>
))}
</div>
</div>
</div>
<div>
<label htmlFor="signup-workspace" className={label}>
Workspace
</label>
<div
data-tour="signup-workspace"
className="flex h-10 items-center rounded-lg bg-white text-sm ring-1 ring-zinc-900/10 transition-shadow focus-within:ring-2 focus-within:ring-zinc-900 dark:bg-zinc-950 dark:ring-white/10 dark:focus-within:ring-zinc-100"
>
<span className="pl-3 text-zinc-400">pulse.app/</span>
<input
id="signup-workspace"
value={workspace}
onChange={(event) => setWorkspace(event.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""))}
placeholder="acme"
className="h-full min-w-0 flex-1 bg-transparent pr-3 text-zinc-900 outline-none placeholder:text-zinc-400 dark:text-zinc-50"
/>
</div>
</div>
<button
data-tour="signup-submit"
type="submit"
className="h-10 w-full rounded-lg bg-zinc-900 text-sm font-medium text-white transition-[background-color,scale] duration-150 hover:bg-zinc-800 active:scale-[0.98] dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
>
{created ? "Workspace created" : "Create workspace"}
</button>
</form>
<SpotlightTour open={open} onOpenChange={setOpen} steps={steps} />
</div>
);
}





























