blocktemplate
Changelog page
A complete changelog page built from Facade UI sections: a header, the releases with their changes, and an optional email sign-up. You supply one content object.
Preview
Installation
pnpm dlx shadcn@latest add @facade/changelogThis component needs changelog-list, container, cta-group, footer, nav-top, newsletter, section, section-header, types. The CLI installs them for you.
Usage
This is the code for the preview above. To install it as a file, run npx shadcn@latest add @facade/changelog-demo.
import { Changelog } from "@registry/templates/changelog"
import { FOOTER_GROUPS, NAV_ITEMS, RELEASES } from "./content"
export function Demo() {
return (
<div className="-m-6 sm:-m-10">
<Changelog
currentPath="#changelog"
content={{
brand: "Facade UI",
nav: [...NAV_ITEMS, { label: "Changelog", href: "#changelog" }],
navActions: [{ label: "Get started", href: "#start" }],
header: {
eyebrow: "Changelog",
title: "What is new in Facade UI",
description: "New sections, fixes and the occasional breaking change.",
actions: [
{ label: "RSS feed", href: "#rss", variant: "outline" },
{
label: "GitHub releases",
href: "https://github.com",
external: true,
variant: "ghost",
},
],
},
releases: RELEASES,
subscribe: {
title: "Get the next release by email",
description: "One email per release. Nothing else.",
},
footer: {
groups: FOOTER_GROUPS,
copyright: "© 2026 Facade UI contributors",
},
}}
/>
</div>
)
}Source
These are the files the CLI copies into your project.
/**
* Changelog — a release history page, assembled from registry sections.
*
* A page header, the releases, and an optional email sign-up for people who
* want the next one in their inbox. Like the other templates it is
* composition: `ChangelogList` does the work and installs on its own too.
*
* a11y: the page title is the only `<h1>`; each release is an `<h2>` inside
* its own `<article>`, so heading navigation steps release by release. The
* sign-up reports its outcome through `Newsletter`'s live region.
*
* Dependencies: react, @/lib/types, @/components/sections/*,
* @/components/ui/*.
*/
import type { ReactNode } from "react"
import type { CtaItem, LinkComponent } from "@/lib/types"
import {
ChangelogList,
type ChangeKind,
type ChangelogEntry,
} from "@/components/sections/changelog-list"
import { Footer, type FooterGroup, type FooterLink } from "@/components/sections/footer"
import { NavTop, type NavItem } from "@/components/sections/nav-top"
import { Newsletter, type NewsletterStatus } from "@/components/sections/newsletter"
import { Container } from "@/components/ui/container"
import { CtaGroup } from "@/components/ui/cta-group"
import { Section } from "@/components/ui/section"
import { SectionHeader } from "@/components/ui/section-header"
export interface ChangelogContent {
brand: ReactNode
nav: NavItem[]
navActions?: CtaItem[]
header: {
eyebrow?: string
title: string
description?: string
/** Links beside the title, such as an RSS feed or the GitHub releases. */
actions?: CtaItem[]
}
/** Newest first. */
releases: ChangelogEntry[]
/** Words for the change kinds, for translation. */
kindLabels?: Partial<Record<ChangeKind, string>>
subscribe?: {
eyebrow?: string
title: string
description?: string
note?: ReactNode
}
footer: {
groups?: FooterGroup[]
brand?: ReactNode
copyright?: ReactNode
legal?: FooterLink[]
}
}
export interface ChangelogProps {
content: ChangelogContent
link?: LinkComponent
/** Marks the active nav item. */
currentPath?: string
/** Shown above the header. Usually a `Banner`. */
banner?: ReactNode
/** Wire this to your own form action or mutation. */
onSubscribe?: (email: string) => void
subscribeStatus?: NewsletterStatus
subscribeError?: string
}
export function Changelog({
content,
link,
currentPath,
banner,
onSubscribe,
subscribeStatus,
subscribeError,
}: ChangelogProps) {
const { header, releases, subscribe, footer } = content
return (
<>
{banner}
<NavTop
brand={content.brand}
items={content.nav}
actions={content.navActions}
link={link}
currentPath={currentPath}
/>
<main id="main">
{/* A div: the h1 names the page, so a second landmark adds nothing. */}
<Section as="div" spacing="md" className="border-b">
<Container size="md">
<SectionHeader
headingLevel={1}
size="lg"
eyebrow={header.eyebrow}
title={header.title}
description={header.description}
actions={
header.actions ? (
<CtaGroup items={header.actions} link={link} size="sm" />
) : undefined
}
/>
</Container>
</Section>
<ChangelogList
items={releases}
kindLabels={content.kindLabels}
link={link}
itemHeadingLevel={2}
spacing="sm"
/>
{subscribe ? (
<Newsletter
eyebrow={subscribe.eyebrow}
title={subscribe.title}
description={subscribe.description}
note={subscribe.note}
onSubmit={onSubscribe}
status={subscribeStatus}
errorMessage={subscribeError}
headingLevel={2}
/>
) : null}
</main>
<Footer
brand={footer.brand ?? content.brand}
groups={footer.groups}
copyright={footer.copyright}
legal={footer.legal}
link={link}
headingLevel={2}
/>
</>
)
}Props
ChangelogProps
| Prop | Type | Default |
|---|---|---|
content*Required | ChangelogContent | — |
link | LinkComponent | — |
currentPathMarks the active nav item. | string | — |
bannerShown above the header. Usually a `Banner`. | ReactNode | — |
onSubscribeWire this to your own form action or mutation. | (email: string) => void | — |
subscribeStatus | NewsletterStatus | — |
subscribeError | string | — |