Rollback Preview

Shows what a rollback reverts, what it can't undo and which versions it skips, then confirms by typing the target's name.

StopsRollback skipped more versions than expected

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/rollback-preview.json

The files land in your project. They're yours to edit.

Usage

rollback-preview-usage.tsx
import { RollbackPreview } from "@/components/ui/rollback-preview"

<RollbackPreview
  className="max-w-md"
  current={{ id: "v42", deployedAt: times.current, label: "Gift notes" }}
  target={{ id: "v40", deployedAt: times.target, label: "Pricing page v2" }}
  skipped={[{ id: "v41", deployedAt: times.v41 }]}
  reverts={["Gift notes at checkout (v42)", "Faster search index (v41)", "PAYMENTS_TIMEOUT 8s → 20s"]}
  stays={["Migration 0193 dropped orders.legacy_code", "1,204 webhooks already sent to Shiprocket"]}
  written={[{ label: "orders", count: 318, note: "v40 ignores their gift notes" }, { label: "sign-ups", count: 52 }]}
  onConfirm={() => new Promise<void>((resolve) => setTimeout(() => (setDone(true), resolve()), 700))}
/>

What it catches

  • Changes that revert, listed
  • What stays: irreversible migrations and data written since, with consequences
  • Newer versions the rollback skips
  • Typed name that matches a different version ("v4" for "v40")
  • Case and whitespace differences, normalised ("V40 " matches "v40")

Props

RollbackPreview props
PropTypeDefault
current* (required)

What is live now.

RollbackVersion—
target* (required)

The version you are rolling back to.

RollbackVersion—
skipped

Versions between target and current that also get undone, newest first.

RollbackVersion[][]
reverts* (required)

Changes the rollback takes away, e.g. "Gift notes at checkout".

string[]—
stays

Things the rollback does NOT undo, e.g. irreversible migrations, sent webhooks, env vars.

string[][]
written

Data written after the target went out, which the old version will have to live with.

RollbackWritten[][]
onConfirm* (required)

Runs the rollback. Return a promise to show a pending state; a rejection re-enables the button.

() => void | Promise<void>—
title

Heading of the preview.

string"Roll back {current} → {target}"
targetMeta

`{age}` and `{count}` (skipped versions) are filled in.

string"{target} went out {age} ago"
skippedText

Says which newer versions the rollback skips.

string"Also undoes {skipped}"
revertsLabel

Heading of the list of changes that revert.

string"Reverts"
staysLabel

Heading of the list of things that won't undo.

string"Won't undo"
writtenLabel

Says what data was written since the target went out.

string"Written since {target}"
confirmLabel

`{target}` is filled in.

string"Roll back to {target}"
inputLabel

Label of the type-the-name input.

string"Type {target} to confirm"
wrongVersionText

Shown when the typed id is another version, `{typed}` and `{target}` filled in.

string"{typed} isn't the target. You're rolling back to {target}."
pendingLabel

Confirm label while onConfirm runs.

string"Rolling back…"

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Name input has a <label>, aria-invalid on a wrong version and a polite hint via aria-describedby
  • Enter in the input confirms when it matches
  • Confirm sets aria-busy while pending
  • Sections use icons plus text counts; motion skipped under reduced motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Authorize the rollback server-side and check that the target is still deployable (schema compatibility) before running it.

  • Lists only what you pass; it cannot inspect your deploys or migrations
  • Typing a name proves attention, not authorization

What it stops

Rollback skipped more versions than expected

Also known as

rollback · revert deploy · redeploy · release · vercel · version history · deployment · downgrade

Field type

Dependencies

Pairs well with