# Jot: Hints That Retire

> A file-sharing card where each hint rubs itself out once you've done what it asks, and the next one writes in.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/jot-onboarding-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/jot-onboarding-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/jot-onboarding-demo/light.png
- Registry item: https://keyframeui.com/r/jot-onboarding-demo.json
- Categories: annotation, svg, onboarding
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/jot-onboarding-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/jot.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/jot#jot-onboarding-demo

## Files

### `registry/demos/jot-onboarding-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { Jot } from "@/components/ui/jot";

const FILE = { name: "Brand guidelines.pdf", size: "4.2 MB" };
const LINK = "parcel.to/k7f2q";

type Step = "empty" | "uploading" | "ready" | "sent";

export function JotOnboardingDemo() {
  const [step, setStep] = React.useState<Step>("empty");
  const dropRef = React.useRef<HTMLButtonElement>(null);
  const copyRef = React.useRef<HTMLButtonElement>(null);

  // A short, pretend upload, so the second hint waits for the file to land.
  React.useEffect(() => {
    if (step !== "uploading") return;
    const timer = window.setTimeout(() => setStep("ready"), 900);
    return () => window.clearTimeout(timer);
  }, [step]);

  const hasFile = step !== "empty";
  const sent = step === "sent";

  return (
    <div
      data-jot-scope
      className="@container/share relative grid w-full max-w-[52rem] justify-items-center px-2 pt-20 pb-24 @2xl/share:px-0 @2xl/share:py-10"
    >
      <div className="w-full max-w-[22rem] rounded-[1.5rem] border border-zinc-950/10 bg-white p-5 shadow-[0_1px_2px_rgb(0_0_0/0.04),0_12px_32px_-12px_rgb(0_0_0/0.12)] dark:border-white/10 dark:bg-zinc-900 dark:shadow-none">
        <div className="flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-400">
          <span className="flex items-center gap-1.5 font-medium text-zinc-950 dark:text-white">
            <svg viewBox="0 0 16 16" aria-hidden className="size-4">
              <path d="M2.5 5.2 8 2.5l5.5 2.7v5.6L8 13.5l-5.5-2.7z" className="fill-amber-400" />
              <path d="M2.5 5.2 8 8l5.5-2.8M8 8v5.5" className="fill-none stroke-amber-700" strokeWidth="1.1" strokeLinejoin="round" />
            </svg>
            Parcel
          </span>
          Links last 7 days
        </div>
        <p className="mt-4 text-lg/tight font-semibold tracking-[-0.02em] text-zinc-950 dark:text-white">Send files</p>

        <button
          ref={dropRef}
          type="button"
          disabled={hasFile}
          onClick={() => setStep("uploading")}
          className="mt-3 grid w-full justify-items-center gap-1 rounded-2xl border border-dashed border-zinc-950/20 bg-zinc-50 px-4 py-6 text-center outline-none transition-colors duration-150 enabled:hover:border-zinc-950/40 enabled:hover:bg-zinc-100/70 focus-visible:ring-2 focus-visible:ring-blue-500 disabled:cursor-default disabled:opacity-50 dark:border-white/15 dark:bg-white/[0.03] dark:enabled:hover:border-white/30 dark:enabled:hover:bg-white/[0.06]"
        >
          <svg viewBox="0 0 20 20" aria-hidden className="size-5 text-zinc-500 dark:text-zinc-400">
            <path d="M10 13V4m0 0L6.5 7.5M10 4l3.5 3.5M4 13.5v1A1.5 1.5 0 0 0 5.5 16h9a1.5 1.5 0 0 0 1.5-1.5v-1" className="fill-none stroke-current" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          <span className="text-sm font-medium text-zinc-950 dark:text-white">Drop files here</span>
          <span className="text-xs text-zinc-500 dark:text-zinc-400">or click to add a sample file</span>
        </button>

        <div className="mt-3 flex h-12 items-center gap-3 rounded-xl px-1" aria-live="polite">
          {hasFile ? (
            <>
              <span className="grid size-9 shrink-0 place-items-center rounded-lg bg-red-50 text-[10px] font-semibold text-red-600 dark:bg-red-500/10 dark:text-red-400">
                PDF
              </span>
              <span className="min-w-0 flex-1">
                <span className="block truncate text-sm font-medium text-zinc-950 dark:text-white">{FILE.name}</span>
                <span className="mt-1 block h-1 overflow-hidden rounded-full bg-zinc-100 dark:bg-white/10">
                  <span
                    className={
                      step === "uploading"
                        ? "block h-full w-full origin-left rounded-full bg-zinc-950 transition-transform duration-[900ms] ease-out starting:scale-x-0 dark:bg-white"
                        : "block h-full w-full rounded-full bg-emerald-500"
                    }
                  />
                </span>
              </span>
              <span className="text-xs text-zinc-500 tabular-nums dark:text-zinc-400">
                {step === "uploading" ? "Uploading" : FILE.size}
              </span>
            </>
          ) : (
            <span className="w-full text-center text-xs text-zinc-400 dark:text-zinc-500">No files yet</span>
          )}
        </div>

        <div className="mt-3 flex items-center gap-2 border-t border-zinc-950/[0.06] pt-4 dark:border-white/[0.06]">
          <span className="min-w-0 flex-1 truncate rounded-full bg-zinc-100 px-3 py-2 font-mono text-xs text-zinc-500 dark:bg-white/5 dark:text-zinc-400">
            {step === "ready" || sent ? LINK : "Your link shows up here"}
          </span>
          <button
            ref={copyRef}
            type="button"
            disabled={step !== "ready" && !sent}
            onClick={() => setStep("sent")}
            className="inline-flex h-9 shrink-0 items-center gap-1.5 rounded-full bg-zinc-950 px-4 text-sm font-medium text-white outline-none transition-opacity duration-150 enabled:hover:bg-zinc-800 focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 disabled:opacity-35 dark:bg-white dark:text-zinc-950 dark:enabled:hover:bg-zinc-200 dark:focus-visible:ring-white dark:focus-visible:ring-offset-zinc-900"
          >
            {sent ? (
              <>
                <svg viewBox="0 0 16 16" aria-hidden className="size-4">
                  <path d="m3.5 8.5 3 3 6-7" className="fill-none stroke-current" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
                Copied
              </>
            ) : (
              "Copy link"
            )}
          </button>
        </div>
      </div>

      <button
        type="button"
        onClick={() => setStep("empty")}
        className={
          "mt-4 rounded-sm text-xs font-medium text-zinc-500 underline decoration-zinc-300 underline-offset-4 outline-none transition-opacity duration-200 hover:text-zinc-950 focus-visible:ring-2 focus-visible:ring-zinc-950 dark:text-zinc-400 dark:decoration-zinc-700 dark:hover:text-white " +
          (sent ? "opacity-100" : "pointer-events-none opacity-0")
        }
        tabIndex={sent ? 0 : -1}
        aria-hidden={!sent}
      >
        Start over
      </button>

      <Jot
        to={dropRef}
        show={step === "empty"}
        bend={0.3}
        tilt={-3}
        className="absolute top-4 left-4 text-xl/snug text-blue-600 @2xl/share:top-[30%] @2xl/share:left-4 dark:text-blue-400"
      >
        start here: drop
        <br />
        anything in
      </Jot>
      <Jot
        to={copyRef}
        show={step === "ready"}
        bend={0.35}
        tilt={2}
        className="absolute right-4 bottom-6 text-xl/snug text-blue-600 @2xl/share:top-[62%] @2xl/share:right-4 @2xl/share:bottom-auto dark:text-blue-400"
      >
        now grab the link
      </Jot>
      <Jot
        show={sent}
        tilt={-2}
        className="absolute bottom-6 left-4 text-xl/snug text-emerald-600 @2xl/share:top-[64%] @2xl/share:bottom-auto @2xl/share:left-4 dark:text-emerald-400"
      >
        that&apos;s the whole thing
      </Jot>
    </div>
  );
}
```
