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

SaaS landing page

A complete SaaS marketing page assembled from registry sections, driven by one content object.

Preview

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

Installation

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

Pulls in cta-band, faq-accordion, feature-grid, feature-rows, footer, hero-split, logo-cloud, logo-mark, nav-top, pricing-tier, pricing-tiers, stat, stats, testimonial, testimonials-grid, types. The CLI installs them for you.

Usage

The exact source of the preview above.

demos/saas-landing.tsx
"use client"

/**
 * `"use client"` because the template's feature grid carries icon components,
 * and NavTop below it is a client component — icons cannot cross a
 * server-to-client boundary as props. See the note on `IconComponent`.
 */

import { SaasLanding } from "@registry/templates/saas-landing"

import {
  ACTIONS,
  FAQS,
  FEATURES,
  FOOTER_GROUPS,
  LOGOS,
  MediaPlaceholder,
  NAV_ITEMS,
  PLANS,
  QUOTES,
  ROWS,
  STATS,
} from "./content"

export function Demo() {
  return (
    <div className="-m-6 sm:-m-10">
      <SaasLanding
        currentPath="#pricing"
        content={{
          brand: "Facade UI",
          nav: NAV_ITEMS,
          navActions: [
            { label: "Sign in", href: "#sign-in", variant: "ghost" },
            { label: "Start free", href: "#start" },
          ],
          hero: {
            eyebrow: "Free and open source",
            title: "The front of every great website",
            description:
              "Marketing sections and a design system you install with the shadcn CLI. The source lands in your project — yours to read, change and own.",
            actions: ACTIONS,
            note: "MIT licensed. No account, no gating.",
            media: <MediaPlaceholder label="Product screenshot" />,
          },
          logos: { title: "Trusted by teams shipping every day", items: LOGOS },
          features: {
            eyebrow: "Features",
            title: "Everything a marketing page needs",
            description: "Composable sections that take typed content and slots.",
            items: FEATURES,
          },
          rows: {
            eyebrow: "How it works",
            title: "Install a piece, own the source",
            items: ROWS,
          },
          stats: {
            eyebrow: "By the numbers",
            title: "What the checks cover",
            items: STATS,
          },
          testimonials: {
            eyebrow: "Customers",
            title: "What teams say",
            items: QUOTES.slice(0, 3),
          },
          pricing: {
            eyebrow: "Pricing",
            title: "Simple, and actually free",
            items: PLANS,
            note: "All prices exclude VAT.",
          },
          faq: { eyebrow: "FAQ", title: "Questions, answered", items: FAQS },
          cta: {
            title: "Ready when you are",
            description:
              "Install the first section and see how it sits in your own theme.",
            actions: [{ label: "Get started", href: "#start" }],
          },
          footer: {
            groups: FOOTER_GROUPS,
            copyright: "© 2026 Facade UI contributors",
            legal: [
              { label: "Privacy", href: "#privacy" },
              { label: "Terms", href: "#terms" },
            ],
          },
        }}
      />
    </div>
  )
}

Source

What the CLI copies into your project, byte for byte.

components/templates/saas-landing.tsx
/**
 * SaasLanding — a complete SaaS marketing page, assembled from registry sections.
 *
 * A template is composition, not a new component: every section below is the
 * same one you can install on its own, given content. That is the point — the
 * template shows how the pieces sit together and is meant to be edited, not
 * configured.
 *
 * Everything is content-driven through one `SaasLandingContent` object, so the
 * page can be rendered from a CMS payload without touching this file. Pass
 * `image` and `link` once and they reach every section that needs them.
 *
 * a11y: the heading outline is the template's job, and it is explicit here
 * rather than left to each section's default. The hero is the page's `<h1>`;
 * every other band is an `<h2>` with its own items at `<h3>`. Because the
 * sections take `headingLevel` as a prop, that holds no matter how the page is
 * rearranged.
 *
 * `<main>` is rendered here, and `NavTop`'s `<header>` and `Footer`'s
 * `<footer>` sit outside it, so the landmark structure is correct for the whole
 * document rather than for a fragment of it.
 *
 * Dependencies: react, @/lib/types, @/components/sections/*.
 */

import type { ReactNode } from "react"

import type { CtaItem, ImageComponent, LinkComponent } from "@/lib/types"
import { CtaBand } from "@/components/sections/cta-band"
import { FaqAccordion, type FaqItem } from "@/components/sections/faq-accordion"
import { FeatureGrid, type FeatureItem } from "@/components/sections/feature-grid"
import { FeatureRows, type FeatureRowItem } from "@/components/sections/feature-rows"
import { Footer, type FooterGroup, type FooterLink } from "@/components/sections/footer"
import { HeroSplit } from "@/components/sections/hero-split"
import { LogoCloud } from "@/components/sections/logo-cloud"
import { NavTop, type NavItem } from "@/components/sections/nav-top"
import { PricingTiers } from "@/components/sections/pricing-tiers"
import { Stats } from "@/components/sections/stats"
import { TestimonialsGrid } from "@/components/sections/testimonials-grid"
import type { LogoItem } from "@/components/ui/logo-mark"
import type { PricingTierItem } from "@/components/ui/pricing-tier"
import type { StatItem } from "@/components/ui/stat"
import type { TestimonialItem } from "@/components/ui/testimonial"

export interface SaasLandingContent {
  brand: ReactNode
  nav: NavItem[]
  navActions?: CtaItem[]

  hero: {
    eyebrow?: string
    title: string
    description: string
    actions: CtaItem[]
    note?: ReactNode
    media?: ReactNode
  }

  logos?: { title?: string; items: LogoItem[] }
  stats?: { title?: string; eyebrow?: string; items: StatItem[] }

  features: {
    eyebrow?: string
    title: string
    description?: string
    items: FeatureItem[]
  }

  rows?: {
    eyebrow?: string
    title?: string
    description?: string
    items: FeatureRowItem[]
  }

  testimonials?: {
    eyebrow?: string
    title: string
    description?: string
    items: TestimonialItem[]
  }

  pricing?: {
    eyebrow?: string
    title: string
    description?: string
    items: PricingTierItem[]
    note?: ReactNode
  }

  faq?: {
    eyebrow?: string
    title: string
    description?: string
    items: FaqItem[]
  }

  cta: {
    eyebrow?: string
    title: string
    description?: string
    actions: CtaItem[]
    note?: ReactNode
  }

  footer: {
    groups?: FooterGroup[]
    brand?: ReactNode
    copyright?: ReactNode
    legal?: FooterLink[]
  }
}

export interface SaasLandingProps {
  content: SaasLandingContent
  image?: ImageComponent
  link?: LinkComponent
  /** Marks the active nav item. */
  currentPath?: string
  /** Rendered above the header — a `Banner`, usually. */
  banner?: ReactNode
}

export function SaasLanding({
  content,
  image,
  link,
  currentPath,
  banner,
}: SaasLandingProps) {
  const { hero, logos, stats, features, rows, testimonials, pricing, faq, cta, footer } =
    content

  return (
    <>
      {banner}

      <NavTop
        brand={content.brand}
        items={content.nav}
        actions={content.navActions}
        link={link}
        currentPath={currentPath}
      />

      <main id="main">
        {/* The only h1 on the page. */}
        <HeroSplit
          eyebrow={hero.eyebrow}
          title={hero.title}
          description={hero.description}
          actions={hero.actions}
          note={hero.note}
          media={hero.media}
          link={link}
          headingLevel={1}
          spacing="lg"
        />

        {logos ? (
          <LogoCloud
            title={logos.title}
            items={logos.items}
            image={image}
            link={link}
            headingLevel={2}
            spacing="sm"
          />
        ) : null}

        <FeatureGrid
          eyebrow={features.eyebrow}
          title={features.title}
          description={features.description}
          items={features.items}
          link={link}
          headingLevel={2}
          itemHeadingLevel={3}
          align="center"
        />

        {rows ? (
          <FeatureRows
            eyebrow={rows.eyebrow}
            title={rows.title}
            description={rows.description}
            items={rows.items}
            link={link}
            headingLevel={2}
            itemHeadingLevel={3}
            align="center"
          />
        ) : null}

        {stats ? (
          <Stats
            eyebrow={stats.eyebrow}
            title={stats.title}
            items={stats.items}
            headingLevel={2}
            className="border-y"
          />
        ) : null}

        {testimonials ? (
          <TestimonialsGrid
            eyebrow={testimonials.eyebrow}
            title={testimonials.title}
            description={testimonials.description}
            items={testimonials.items}
            image={image}
            headingLevel={2}
          />
        ) : null}

        {pricing ? (
          <PricingTiers
            eyebrow={pricing.eyebrow}
            title={pricing.title}
            description={pricing.description}
            items={pricing.items}
            note={pricing.note}
            link={link}
            headingLevel={2}
            tierHeadingLevel={3}
          />
        ) : null}

        {faq ? (
          <FaqAccordion
            eyebrow={faq.eyebrow}
            title={faq.title}
            description={faq.description}
            items={faq.items}
            headingLevel={2}
            itemHeadingLevel={3}
            align="center"
            schemaOrg
          />
        ) : null}

        <CtaBand
          eyebrow={cta.eyebrow}
          title={cta.title}
          description={cta.description}
          actions={cta.actions}
          note={cta.note}
          link={link}
          headingLevel={2}
          variant="primary"
        />
      </main>

      <Footer
        brand={footer.brand ?? content.brand}
        groups={footer.groups}
        copyright={footer.copyright}
        legal={footer.legal}
        link={link}
        headingLevel={2}
      />
    </>
  )
}

Props

SaasLandingProps

Props for SaasLandingProps
PropTypeDefault
content*RequiredSaasLandingContent
imageImageComponent
linkLinkComponent
currentPathMarks the active nav item.string
bannerRendered above the header — a `Banner`, usually.ReactNode