shadcn.io is an independent project built on shadcn/ui
View Changelog opens a dialog titled Changelog. The header and its border stay put. Four releases scroll under it, from Version 2.1.0 through Version 1.8.0, each with a short list. Close sits in the footer.
Use it when a long list still needs its title in view. A changelog people keep open can live in a Sheet. In React, Base UI does not load older releases.
Installation
Anatomy

Usage
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
View Changelog
</DialogTrigger>
<DialogContent className="gap-0 p-0 sm:max-w-lg">
<DialogHeader className="border-b px-6 pt-6 pb-4">
<DialogTitle>Changelog</DialogTitle>
<DialogDescription>
Recent updates and improvements to our platform.
</DialogDescription>
</DialogHeader>
<ScrollArea className="h-100">
<div className="flex flex-col gap-6 px-6 py-4">
{releases.map((release) => (
<section key={release.version}>
<h3 className="mb-2 font-semibold">{release.version}</h3>
<ul className="ms-4 flex list-disc flex-col gap-1 text-muted-foreground text-sm">
{release.changes.map((change) => (
<li key={change}>{change}</li>
))}
</ul>
</section>
))}
</div>
</ScrollArea>
<DialogFooter className="border-t px-6 py-4">
<DialogClose render={<Button />}>Close</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>FAQ
How does the header stay in place?
Only the scroll area scrolls. DialogContent takes gap-0 p-0, the header gets its own padding and a border-b, and the scroll area sits between it and the footer.
Why the border under the header?
It marks where the fixed part ends and the scrolling part starts. The footer has a matching border-t.
API Reference
Dialog
No extra props in this demo. The rest is on the Dialog page.
Button
Sets variant="outline". The rest is on the Button page.
Scroll Area
No extra props in this demo. The rest is on the Scroll Area page.





