# Jot: Annotated Hero

> A form builder's hero. Two notes write themselves in, one curling its arrow up to the call to action, the other pointing at the live reply count.

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-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/jot-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/jot-demo/light.png
- Registry item: https://keyframeui.com/r/jot-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-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-demo

## Files

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

```tsx
"use client";

import * as React from "react";

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

// The last twelve hours of replies, oldest first; the newest bar grows as replies come in.
const HOURS = [18, 26, 22, 35, 41, 38, 52, 61, 48, 57, 66, 44];

export function JotDemo() {
  const ctaRef = React.useRef<HTMLAnchorElement>(null);
  const statsRef = React.useRef<HTMLDivElement>(null);
  const [replies, setReplies] = React.useState(1284);

  // A reply or two every couple of seconds, so "live" is true.
  React.useEffect(() => {
    const timer = window.setInterval(() => setReplies((n) => n + 1 + Math.floor(Math.random() * 2)), 2200);
    return () => window.clearInterval(timer);
  }, []);

  const latest = HOURS[HOURS.length - 1] + (replies - 1284) * 2;
  const top = Math.max(...HOURS, latest);

  return (
    <div data-jot-scope className="@container/jot relative w-full max-w-[56rem] px-2 pt-24 pb-32 @lg/jot:px-6 @3xl/jot:pt-28 @3xl/jot:pb-40">
      <div className="mx-auto grid max-w-[38rem] gap-8 rounded-[1.75rem] border border-zinc-950/10 bg-white p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04),0_12px_32px_-12px_rgb(0_0_0/0.12)] @lg/jot:grid-cols-[1fr_12rem] @lg/jot:p-8 dark:border-white/10 dark:bg-zinc-900 dark:shadow-none">
        <div className="flex flex-col">
          <p className="flex items-center gap-2 text-[13px] font-medium text-zinc-500 dark:text-zinc-400">
            <svg viewBox="0 0 20 20" aria-hidden className="size-5">
              <rect width="20" height="20" rx="6" className="fill-zinc-950 dark:fill-white" />
              <path
                d="M5.5 7.5 7.6 13l2.4-5 2.4 5 2.1-5.5"
                className="fill-none stroke-white dark:stroke-zinc-950"
                strokeWidth="1.6"
                strokeLinecap="round"
                strokeLinejoin="round"
              />
            </svg>
            Wren Forms
          </p>
          <h2 className="mt-5 text-[2rem]/[1.05] font-semibold tracking-[-0.03em] text-balance text-zinc-950 dark:text-white">
            Forms people actually finish.
          </h2>
          <p className="mt-3 max-w-[30ch] text-[15px]/6 text-pretty text-zinc-600 dark:text-zinc-400">
            One question at a time, saved as they type, easy to answer on a phone.
          </p>
          <div className="mt-7 flex flex-wrap items-center gap-x-5 gap-y-3">
            <a
              ref={ctaRef}
              href="#build"
              className="inline-flex h-10 items-center gap-1.5 rounded-full bg-zinc-950 px-5 text-sm font-medium text-white outline-none hover:bg-zinc-800 focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 dark:focus-visible:ring-white dark:focus-visible:ring-offset-zinc-900"
            >
              Build a form
              <svg viewBox="0 0 16 16" aria-hidden className="size-4">
                <path d="M6 4l4 4-4 4" className="fill-none stroke-current" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </a>
            <a
              href="#templates"
              className="rounded-sm text-sm font-medium text-zinc-600 underline decoration-zinc-300 underline-offset-4 outline-none hover:text-zinc-950 hover:decoration-zinc-950 focus-visible:ring-2 focus-visible:ring-zinc-950 dark:text-zinc-400 dark:decoration-zinc-700 dark:hover:text-white dark:hover:decoration-white dark:focus-visible:ring-white"
            >
              See templates
            </a>
          </div>
        </div>

        <div
          ref={statsRef}
          className="order-first self-end rounded-2xl @lg/jot:order-none border border-zinc-950/[0.07] bg-zinc-50 p-4 dark:border-white/[0.07] dark:bg-zinc-950/60"
        >
          <div className="flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-400">
            <span>Replies today</span>
            <span className="flex items-center gap-1.5">
              <span className="size-1.5 rounded-full bg-emerald-500" />
              Live
            </span>
          </div>
          <p className="mt-1.5 text-[1.75rem]/none font-semibold tracking-tight text-zinc-950 tabular-nums dark:text-white">
            {replies.toLocaleString("en-US")}
          </p>
          <div className="mt-4 flex h-12 items-end gap-[3px]" aria-hidden>
            {[...HOURS.slice(0, -1), latest].map((h, i) => (
              <span
                key={i}
                className={
                  i === HOURS.length - 1
                    ? "flex-1 rounded-[2px] bg-zinc-950 transition-[height] duration-500 ease-out dark:bg-white"
                    : "flex-1 rounded-[2px] bg-zinc-300 dark:bg-zinc-700"
                }
                style={{ height: `${(h / top) * 100}%` }}
              />
            ))}
          </div>
          <p className="mt-3 flex justify-between text-xs text-zinc-500 tabular-nums dark:text-zinc-400">
            <span>Finish rate</span>
            <span className="font-medium text-zinc-950 dark:text-white">71%</span>
          </p>
        </div>
      </div>

      <Jot
        to={ctaRef}
        loop
        bend={-0.35}
        tilt={-3}
        className="absolute bottom-5 left-4 max-w-44 text-xl/snug text-blue-600 @lg/jot:left-6 @3xl/jot:bottom-7 @3xl/jot:left-4 dark:text-blue-400"
      >
        free for your first 1,000 replies
      </Jot>
      <Jot
        to={statsRef}
        bend={0.3}
        tilt={2}
        delay={0.7}
        className="absolute top-4 right-4 max-w-none text-xl/snug text-blue-600 @lg/jot:right-6 @3xl/jot:top-6 @3xl/jot:right-4 dark:text-blue-400"
      >
        real replies,
        <br />
        counted as they land
      </Jot>
    </div>
  );
}
```
