blocktemplate
Product launch
A single-product launch page: announcement banner, bento highlights, tabbed detail and one email capture.
Preview
Installation
pnpm dlx shadcn@latest add https://facadeui.dev/r/product-launch.jsonPulls in banner, bento-grid, faq-accordion, feature-tabs, footer, hero-centered, nav-top, newsletter, types, usp-list. The CLI installs them for you.
Usage
The exact source of the preview above.
"use client"
import { useState } from "react"
import { ProductLaunch } from "@registry/templates/product-launch"
import type { NewsletterStatus } from "@registry/sections/newsletter"
import {
BENTO,
FAQS,
FOOTER_GROUPS,
NAV_ITEMS,
TABS,
USPS,
MediaPlaceholder,
} from "./content"
export function Demo() {
const [status, setStatus] = useState<NewsletterStatus>("idle")
return (
<div className="-m-6 sm:-m-10">
<ProductLaunch
signUpStatus={status}
signUpError="Enter a valid email address."
onSignUp={(email) => {
if (!email.includes("@")) {
setStatus("error")
return
}
setStatus("submitting")
window.setTimeout(() => setStatus("success"), 900)
}}
content={{
brand: "Facade UI",
nav: NAV_ITEMS,
navActions: [{ label: "Star on GitHub", href: "#github" }],
announcement: {
label: "Launch announcement",
message: "Facade UI v0.1 is out.",
action: { label: "Read the notes", href: "#release" },
},
hero: {
eyebrow: "Now available",
title: "Sixty-two pieces, one system",
description:
"Sections, atoms, motion and three verified themes. Install what you need; the source is yours.",
actions: [
{ label: "Get started", href: "#start" },
{ label: "Browse components", href: "#components" },
],
note: "MIT licensed.",
media: <MediaPlaceholder label="Launch screenshot" ratio="aspect-[16/8]" />,
},
highlights: {
eyebrow: "At a glance",
title: "What is in the box",
items: BENTO,
},
usps: { eyebrow: "Why", title: "Three things that matter", items: USPS },
detail: {
eyebrow: "How it works",
title: "Three steps, one system",
items: TABS,
},
faq: { eyebrow: "FAQ", title: "Questions, answered", items: FAQS },
signup: {
eyebrow: "Changelog",
title: "One email when something ships",
description: "No drip campaign, no webinar invitations.",
note: "Unsubscribe in one click.",
},
footer: {
groups: FOOTER_GROUPS,
copyright: "© 2026 Facade UI contributors",
legal: [{ label: "Privacy", href: "#privacy" }],
},
}}
/>
</div>
)
}Source
What the CLI copies into your project, byte for byte.
/**
* ProductLaunch — a single-product launch page.
*
* The narrowest of the three templates, and deliberately so: a launch page has
* one job. A dismissible banner, a centred hero, the bento grid that shows the
* thing off, a short USP strip, tabbed detail for people who want it, an FAQ,
* and one email capture. No pricing table, no team — both dilute a launch.
*
* a11y: the banner is a named landmark rather than a live region, because a
* banner present on load is not an update and announcing it interrupts. The
* newsletter reports its own outcome through a live region that exists from the
* first paint.
*
* Dependencies: react, @/lib/types, @/components/sections/*.
*/
import type { ReactNode } from "react"
import type { CtaItem, LinkComponent } from "@/lib/types"
import { Banner } from "@/components/sections/banner"
import { BentoGrid, type BentoItem } from "@/components/sections/bento-grid"
import { FaqAccordion, type FaqItem } from "@/components/sections/faq-accordion"
import { FeatureTabs, type FeatureTabItem } from "@/components/sections/feature-tabs"
import { Footer, type FooterGroup, type FooterLink } from "@/components/sections/footer"
import { HeroCentered } from "@/components/sections/hero-centered"
import { NavTop, type NavItem } from "@/components/sections/nav-top"
import { Newsletter, type NewsletterStatus } from "@/components/sections/newsletter"
import { UspList, type UspItem } from "@/components/sections/usp-list"
export interface ProductLaunchContent {
brand: ReactNode
nav: NavItem[]
navActions?: CtaItem[]
announcement?: {
/** Names the landmark. */
label?: string
message: ReactNode
action?: { label: string; href: string; external?: boolean }
/** Remembers the dismissal across visits. */
storageKey?: string
}
hero: {
eyebrow?: string
title: string
description: string
actions: CtaItem[]
note?: ReactNode
banner?: ReactNode
media?: ReactNode
}
highlights: {
eyebrow?: string
title: string
description?: string
items: BentoItem[]
}
usps?: { title?: string; eyebrow?: string; items: UspItem[] }
detail?: {
eyebrow?: string
title: string
description?: string
items: FeatureTabItem[]
}
faq?: {
eyebrow?: string
title: string
items: FaqItem[]
}
signup: {
eyebrow?: string
title: string
description?: string
note?: ReactNode
}
footer: {
groups?: FooterGroup[]
brand?: ReactNode
copyright?: ReactNode
legal?: FooterLink[]
}
}
export interface ProductLaunchProps {
content: ProductLaunchContent
/**
* No `image` prop, unlike the other two templates: every visual in this page
* comes through a `media` ReactNode slot, so there is no data-driven image
* list for a component type to be threaded into. Pass `<Image />` directly.
*/
link?: LinkComponent
currentPath?: string
/** Wire this to your own form action or mutation. */
onSignUp?: (email: string) => void
signUpStatus?: NewsletterStatus
signUpError?: string
}
export function ProductLaunch({
content,
link,
currentPath,
onSignUp,
signUpStatus,
signUpError,
}: ProductLaunchProps) {
const { announcement, hero, highlights, usps, detail, faq, signup, footer } = content
return (
<>
{announcement ? (
<Banner
label={announcement.label ?? "Announcement"}
action={announcement.action}
storageKey={announcement.storageKey}
link={link}
dismissible
>
{announcement.message}
</Banner>
) : null}
<NavTop
brand={content.brand}
items={content.nav}
actions={content.navActions}
link={link}
currentPath={currentPath}
/>
<main id="main">
<HeroCentered
eyebrow={hero.eyebrow}
title={hero.title}
description={hero.description}
actions={hero.actions}
note={hero.note}
banner={hero.banner}
media={hero.media}
link={link}
headingLevel={1}
size="xl"
spacing="lg"
/>
<BentoGrid
eyebrow={highlights.eyebrow}
title={highlights.title}
description={highlights.description}
items={highlights.items}
link={link}
headingLevel={2}
itemHeadingLevel={3}
align="center"
/>
{usps ? (
<UspList
eyebrow={usps.eyebrow}
title={usps.title}
items={usps.items}
headingLevel={2}
itemHeadingLevel={3}
align="center"
className="border-y"
/>
) : null}
{detail ? (
<FeatureTabs
eyebrow={detail.eyebrow}
title={detail.title}
description={detail.description}
items={detail.items}
headingLevel={2}
itemHeadingLevel={3}
/>
) : null}
{faq ? (
<FaqAccordion
eyebrow={faq.eyebrow}
title={faq.title}
items={faq.items}
headingLevel={2}
itemHeadingLevel={3}
align="center"
schemaOrg
/>
) : null}
<Newsletter
eyebrow={signup.eyebrow}
title={signup.title}
description={signup.description}
note={signup.note}
onSubmit={onSignUp}
status={signUpStatus}
errorMessage={signUpError}
headingLevel={2}
/>
</main>
<Footer
brand={footer.brand ?? content.brand}
groups={footer.groups}
copyright={footer.copyright}
legal={footer.legal}
link={link}
headingLevel={2}
/>
</>
)
}Props
ProductLaunchProps
| Prop | Type | Default |
|---|---|---|
content*Required | ProductLaunchContent | — |
linkNo `image` prop, unlike the other two templates: every visual in this page
comes through a `media` ReactNode slot, so there is no data-driven image
list for a component type to be threaded into. Pass `<Image />` directly. | LinkComponent | — |
currentPath | string | — |
onSignUpWire this to your own form action or mutation. | (email: string) => void | — |
signUpStatus | NewsletterStatus | — |
signUpError | string | — |