# Sticky Version Jump Sidebar

> Changelog page with a sticky version-jump sidebar beside long-form release entries grouped into Added, Improved, and Fixed changes.

A shadcn/ui block from the [@7ovr](https://7ovr.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/7ovr/changelog-5).

- Page, with a live preview: https://shadcn.exchange/r/7ovr/changelog-5
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/changelog-5-light-489cddd041f8b6d0.png
- Registry item: https://7ovr.com/r/changelog-5.json
- Categories: changelog
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/changelog-5.json
```

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

## Dependencies

- npm packages: `@base-ui/react`
- Registry items: `badge`

## Files

### `registry/blocks/changelog/5/changelog-block.tsx`

```tsx
import { Badge } from "@/components/ui/badge"

type ChangeType = "Added" | "Improved" | "Fixed"

const typeVariant: Record<ChangeType, "default" | "secondary" | "outline"> = {
  Added: "default",
  Improved: "secondary",
  Fixed: "outline",
}

const releases = [
  {
    version: "2.4.0",
    date: "July 18, 2026",
    summary: "Real-time collaboration and a faster editor.",
    changes: [
      {
        type: "Added" as ChangeType,
        text: "Live cursors and presence in the editor.",
      },
      {
        type: "Added" as ChangeType,
        text: "Slash commands for inserting blocks.",
      },
      {
        type: "Improved" as ChangeType,
        text: "Editor now loads 40% faster on large documents.",
      },
      {
        type: "Fixed" as ChangeType,
        text: "Pasted images occasionally lost their aspect ratio.",
      },
    ],
  },
  {
    version: "2.3.1",
    date: "July 2, 2026",
    summary: "Stability fixes and export improvements.",
    changes: [
      {
        type: "Improved" as ChangeType,
        text: "PDF exports now preserve custom fonts.",
      },
      {
        type: "Fixed" as ChangeType,
        text: "Workspace switcher could show stale membership.",
      },
    ],
  },
  {
    version: "2.3.0",
    date: "June 20, 2026",
    summary: "Introducing scheduled reports.",
    changes: [
      {
        type: "Added" as ChangeType,
        text: "Schedule reports to send on a recurring cadence.",
      },
      {
        type: "Improved" as ChangeType,
        text: "Redesigned the notifications center.",
      },
    ],
  },
]

function slug(version: string) {
  return `v${version.replace(/\./g, "-")}`
}

export default function ChangelogBlock() {
  return (
    <section className="w-full bg-background px-6 py-16 text-foreground">
      <div className="mx-auto w-full max-w-4xl">
        <div className="mb-10 flex flex-col gap-2">
          <p className="text-xs font-semibold tracking-widest text-muted-foreground uppercase">
            Changelog
          </p>
          <h1 className="font-heading text-3xl font-bold tracking-tight">
            What&apos;s new
          </h1>
          <p className="text-sm text-muted-foreground">
            Every improvement, fix, and new feature we ship to Acme.
          </p>
        </div>

        <div className="grid grid-cols-1 gap-10 md:grid-cols-[10rem_1fr]">
          <aside className="hidden md:block">
            <nav className="sticky top-8 flex flex-col gap-1">
              <span className="mb-1 text-[10px] font-semibold tracking-widest text-muted-foreground uppercase">
                Releases
              </span>
              {releases.map((release) => (
                <a
                  key={release.version}
                  href={`#${slug(release.version)}`}
                  className="flex flex-col border-l border-border py-1 pl-3 text-sm transition-colors hover:border-foreground hover:text-foreground"
                >
                  <span className="font-medium tabular-nums">
                    v{release.version}
                  </span>
                  <span className="text-xs text-muted-foreground">
                    {release.date}
                  </span>
                </a>
              ))}
            </nav>
          </aside>

          <div className="flex flex-col gap-12">
            {releases.map((release) => (
              <article
                key={release.version}
                id={slug(release.version)}
                className="flex scroll-mt-8 flex-col gap-4"
              >
                <div className="flex flex-col gap-1">
                  <div className="flex items-center gap-3">
                    <h2 className="font-heading text-xl font-bold tracking-tight tabular-nums">
                      v{release.version}
                    </h2>
                    <span className="text-xs text-muted-foreground">
                      {release.date}
                    </span>
                  </div>
                  <p className="text-sm text-muted-foreground">
                    {release.summary}
                  </p>
                </div>
                <ul className="flex flex-col gap-2.5 border-t border-border pt-4">
                  {release.changes.map((change, index) => (
                    <li key={index} className="flex items-start gap-3 text-sm">
                      <Badge
                        variant={typeVariant[change.type]}
                        className="mt-0.5 w-20 shrink-0 justify-center text-[11px]"
                      >
                        {change.type}
                      </Badge>
                      <span className="text-foreground">{change.text}</span>
                    </li>
                  ))}
                </ul>
              </article>
            ))}
          </div>
        </div>
      </div>
    </section>
  )
}
```
