# Vertical Release Timeline

> Changelog as a vertical timeline with a connected dot rail, each release showing a version badge, date, title, and color-coded changes.

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

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

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/changelog-4.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/4/changelog-block.tsx`

```tsx
import { Badge } from "@/components/ui/badge"
import { IconPlaceholder } from "@/components/icons/icon-placeholder"

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

type Release = {
  version: string
  date: string
  title: string
  changes: { type: ChangeType; text: string }[]
}

const typeStyles: Record<ChangeType, string> = {
  Added: "text-success",
  Improved: "text-info",
  Fixed: "text-warning",
}

const releases: Release[] = [
  {
    version: "3.4.0",
    date: "Jul 9, 2026",
    title: "Saved views and faster search",
    changes: [
      {
        type: "Added",
        text: "Save any filtered view and pin it to the sidebar",
      },
      { type: "Improved", text: "Search now returns results in under 100ms" },
      { type: "Fixed", text: "Keyboard shortcuts no longer fire while typing" },
    ],
  },
  {
    version: "3.3.0",
    date: "Jun 28, 2026",
    title: "Team roles and audit log",
    changes: [
      { type: "Added", text: "Granular member roles for owners and editors" },
      { type: "Added", text: "Exportable audit log for the last ninety days" },
      { type: "Improved", text: "Invitations now expire after seven days" },
    ],
  },
  {
    version: "3.2.1",
    date: "Jun 12, 2026",
    title: "Stability and polish",
    changes: [
      { type: "Fixed", text: "Resolved a rare crash when reordering columns" },
      { type: "Improved", text: "Reduced initial bundle size by roughly 18%" },
    ],
  },
]

function ReleaseEntry({ release }: { release: Release }) {
  return (
    <div className="relative flex gap-6 pb-10 last:pb-0">
      <div className="absolute top-2 bottom-0 left-[5px] w-px bg-border" />
      <span className="relative z-10 mt-1.5 size-2.5 shrink-0 rounded-full bg-foreground ring-4 ring-background" />
      <div className="min-w-0 flex-1">
        <div className="flex items-center gap-3">
          <Badge className="font-mono">{release.version}</Badge>
          <span className="text-xs text-muted-foreground tabular-nums">
            {release.date}
          </span>
        </div>
        <h3 className="mt-3 font-heading text-lg font-semibold tracking-tight text-foreground">
          {release.title}
        </h3>
        <ul className="mt-3 flex flex-col gap-2">
          {release.changes.map((change, i) => (
            <li key={i} className="flex items-baseline gap-2.5 text-sm">
              <span
                className={
                  "w-16 shrink-0 text-xs font-medium " + typeStyles[change.type]
                }
              >
                {change.type}
              </span>
              <span className="text-pretty text-muted-foreground">
                {change.text}
              </span>
            </li>
          ))}
        </ul>
      </div>
    </div>
  )
}

export default function ChangelogBlock() {
  return (
    <section className="flex min-h-svh w-full justify-center bg-background px-6 py-16 text-foreground">
      <div className="w-full max-w-2xl">
        <div className="mb-12 flex flex-col items-start">
          <Badge variant="secondary">
            <IconPlaceholder
              lucide="Flashlight"
              tabler="IconBulb"
              hugeicons="FlashlightIcon"
              phosphor="Flashlight"
              remixicon="RiFlashlightLine"
              data-icon="inline-start"
              aria-hidden="true"
            />
            Changelog
          </Badge>
          <h2 className="mt-4 font-heading text-2xl font-bold tracking-tight text-balance sm:text-3xl">
            What&apos;s New
          </h2>
          <p className="mt-2 text-pretty text-muted-foreground">
            Product updates and improvements, shipped every couple of weeks.
          </p>
        </div>

        <div>
          {releases.map((release) => (
            <ReleaseEntry key={release.version} release={release} />
          ))}
        </div>
      </div>
    </section>
  )
}
```
