# Spotlight Tour: Form Help

> A sign-up form that explains each field in turn. You can type into each field while it's highlighted.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/spotlight-tour-form-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/spotlight-tour-form-demo
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/keyframeui/spotlight-tour-form-demo-light-630dbd6aacad5934.png
- Registry item: https://keyframeui.com/r/spotlight-tour-form-demo.json
- Listed: 2026-10-14, source last changed 2026-10-14

## Install

```bash
npx shadcn@latest add https://keyframeui.com/r/spotlight-tour-form-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/spotlight-tour.json`

## Files

### `registry/demos/spotlight-tour-form-demo.tsx`

```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>
  );
}
```
