shadcn.io is not affiliated with official shadcn/ui
Version History Dialog Block for React
A docs tool's version history sheet shown open: six versions with authors and times, the diff of the chosen one against the current text marked with underline, strike-through, glyphs and words, and a two-step restore.
Installation
Overview
Let people look back at a document and bring a version home, with this version history sheet for React with Base UI. It opens from the Roadmap memo of Marginfold, a collaborative docs tool: the page's header holds an outline Version history Button, and the memo, a top bar with avatars, a real title and a real first paragraph, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Sheet opens at the end of the page: at its start a list of six versions, each with an initials tile, the author, a time such as "Today, 10:42", a line about the change and a "Current" Badge, and beside it the diff of the chosen version against the current text. Insertions are underlined with a plus glyph and deletions struck through with a minus glyph, and each is read out as "added" or "removed", so the diff never relies on colour. Restoring asks first, "Restore v4? You can undo it", and adds a new top version, "Restored from v4", with the only Shine Button. The drawn memo's title and paragraph become the restored text. For preview the sheet is drawn open in place when the page loads; the header button opens the real Sheet, with the same versions. For documents, wikis, designs, settings and anything that keeps a history.
FAQ
Why is the sheet already open, and what do I keep?
So you can see it without a click. On load the sheet is a copy drawn in place at the end of the frame: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Version history button opens the real Sheet, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same HistoryBody and HistoryFoot components and share the block's state, so the version you chose in the copy is chosen in the real one. In your app keep the Sheet and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
Which side does the sheet open from?
From the end of the page's reading direction: the right in a left-to-right page and the left in a right-to-left one. The sheet's own sides are physical, so sideOf reads the page's direction when the sheet opens and passes the matching one. The copy shown on load uses logical positions, so it is already on the right side in either direction. On a phone the copy docks to the foot of the frame as a bottom sheet, and the diff opens under the chosen version instead of beside the list.
How is the diff made, and how do I read it?
It compares the current text with the chosen version word by word, with a longest-common-subsequence match, and shows what restoring the chosen version would do: words it adds are underlined with a plus, words it removes are struck through with a minus, and everything else is plain. A legend names both marks, each run is read out as "added" or "removed" by a screen reader, and a line under the text counts the words, "2 words added, 9 words removed". The title and the first paragraph are compared separately. Choosing the current version says so instead of showing a diff.
How does Restore work, and can I undo it?
Restore this version asks in the foot, "Restore v4? You can undo it", and the same button then reads "Confirm restore of v4". Confirming adds a new top version, "Restored from v4", with that version's title and paragraph, makes it the current one and selects it; nothing older is changed or lost. Undo is always in the foot, quiet until there is something to take back, and removes the restored version. Cancel and Escape step back from the question before they close the sheet.
What stays local in the demo, and how do I wire it up?
All of it. The six versions are typed, the times are typed labels, and Restore only changes the drawn memo in this tab. In your app load the versions from your API in the same shape, post the restore from restore and let the server write the new version; keep diffOf for the comparison, or use the server's own.
Is it accessible from the keyboard?
Yes. The real sheet is named by its title and described by its sentence, focus starts on the chosen version and stays inside until the sheet closes, and Escape, Cancel and the close button return focus to Version history. The versions are buttons, the chosen one marked as current, and every change is said in a polite message. The trigger is passed to SheetTrigger through render, and the sheet is controlled with open and onOpenChange. The copy shown on load moves no focus; Escape inside it, Cancel and its close button close it and put focus on the button.
Related
- Pro
Diff Revision History Blog
Stats and draftsThe revision history of one post: five revisions as a radio group at the start, and at the end a framed word-by-word diff of the lede with inserted and deleted marks for the chosen revision.
- Pro
Rollback History Changelog
Deploys and rolloutsA table of rollbacks that runs rail to rail and ends the block: the day, the version it came from and the one restored, a reason badge, a sortable time to revert and how each ended, with a summary counted from the rows.
- Pro
Diff Viewer Changelog
Code and commitsOne config file's diff in a framed, focusable region, with Previous change and Next change buttons that move a ring from hunk to hunk and a live count.
- Pro
Save Draft Dialog
Notes and editingA publishing editor with its save-draft dialog shown open: a name, a folder, an autosave choice with a sentence that follows it, and a discard that asks again in the same row.
- Pro
Search Replace Dialog
Drawers and popoversA document editor's find bar shown open over real text: every match marked with an underline, the current one ringed and scrolled into view, a count like 3 of 12, case and whole-word toggles, Replace and Replace all.
- Pro
Assign Task Dialog
Side sheetsA sprint board with a side sheet that assigns its one unowned task shown open: five people with their open tasks and load, a priority, a due date from chips or an inline calendar, and a switch to notify the assignee.