Skip to content
Facade UI
Colour mode
GitHub (opens in a new tab)
blocktemplate

Product launch

A single-product launch page: announcement banner, bento highlights, tabbed detail and one email capture.

Preview

Open full width (opens in a new tab)
Preview width

Installation

Package manager
pnpm dlx shadcn@latest add https://facadeui.dev/r/product-launch.json

Pulls 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.

demos/product-launch.tsx
"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.

components/templates/product-launch.tsx
/**
 * 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

Props for ProductLaunchProps
PropTypeDefault
content*RequiredProductLaunchContent
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
currentPathstring
onSignUpWire this to your own form action or mutation.(email: string) => void
signUpStatusNewsletterStatus
signUpErrorstring