Skip to content
All examples

Landing page: classic

A centered hero with a command to copy, a wall of live components, six features, three install steps, a list of examples, questions and a closing call to action. Its own header and footer are optional.

Add this example to a project that has prfct installed. The command writes the files below and installs the components they use.

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/landing.json
components/landing/landing.tsx
"use client"

import * as React from "react"
import Link from "next/link"
import {
  AccessibilityIcon,
  ArrowRightIcon,
  CheckIcon,
  CodeIcon,
  ContrastIcon,
  CopyIcon,
  LanguagesIcon,
  LayoutTemplateIcon,
  PaletteIcon,
} from "lucide-react"

import Showcase from "./landing-showcase"
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"
import { Badge } from "@/components/ui/badge"
import { Button, buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"

const install = "pnpm dlx shadcn@latest add https://prfct.dev/r/prfct.json"

// Links that start with "/" are prfct.dev's own pages: point them at yours.
const navigation = [
  { label: "Features", href: "#features" },
  { label: "Examples", href: "#examples" },
  { label: "Questions", href: "#questions" },
]

const features = [
  {
    icon: ContrastIcon,
    title: "Contrast you don't have to check",
    text: "Every scale is generated, and its text steps are tested against WCAG AA on the fills they sit on, in light and dark.",
  },
  {
    icon: AccessibilityIcon,
    title: "Accessible from the first render",
    text: "Keyboard support, focus management and ARIA come with every component, and automated tests check them in both themes.",
  },
  {
    icon: CodeIcon,
    title: "Code you own",
    text: "The shadcn CLI copies components into your project. Read them, change them, delete what you don't need.",
  },
  {
    icon: PaletteIcon,
    title: "One theme, any brand",
    text: "Pick a color in the theme builder and paste one stylesheet. Every component follows it.",
  },
  {
    icon: LayoutTemplateIcon,
    title: "Whole screens, not only parts",
    text: "A dashboard, a checkout, a mail client and more: example apps you install with one command and adapt.",
  },
  {
    icon: LanguagesIcon,
    title: "Ready for more than English",
    text: "The text built into components, dates and numbers follow the language of your page.",
  },
]

const steps = [
  {
    title: "Start a project",
    text: "Create a Next.js app with shadcn. In an app you already have, leave out --template next.",
    command:
      "pnpm dlx shadcn@latest init --template next --base base --preset nova",
  },
  {
    title: "Add the theme",
    text: "Tokens, base styles and font faces arrive as files in your project. The guide shows the stylesheet import and the two font files to download.",
    command: install,
  },
  {
    title: "Add what you need",
    text: "A single component or a whole example app. Each one brings what it depends on.",
    command: "pnpm dlx shadcn@latest add https://prfct.dev/r/dashboard.json",
  },
]

const examples = [
  {
    name: "Dashboard",
    text: "Navigation, stats, a traffic chart and a deployments table.",
    href: "/examples/dashboard",
  },
  {
    name: "Mail",
    text: "Folders, a message list and a reading pane with a reply box.",
    href: "/examples/mail",
  },
  {
    name: "Checkout",
    text: "Contact, delivery and payment with an order summary in view.",
    href: "/examples/checkout",
  },
  {
    name: "Booking",
    text: "A calendar, free time slots, a short form and a confirmation.",
    href: "/examples/booking",
  },
  {
    name: "Team",
    text: "Members, roles that change in place and an invite dialog.",
    href: "/examples/team",
  },
  {
    name: "Pricing",
    text: "Three plans, a comparison table and answers to questions.",
    href: "/examples/pricing",
  },
]

const questions = [
  {
    question: "Is prfct free?",
    answer:
      "Yes. It is open source under the MIT license, for personal and commercial work.",
  },
  {
    question: "How is it different from shadcn?",
    answer:
      "prfct is built on shadcn's components and keeps their API. It adds generated tokens with tested contrast, a quieter look, documentation for every component and example apps.",
  },
  {
    question: "Do I need Next.js?",
    answer:
      "prfct is built and tested for Next.js. The components are React and Tailwind CSS, so other React frameworks may work, but nobody has tested them.",
  },
  {
    question: "Can I use my own brand color?",
    answer:
      "Yes. The theme builder regenerates every scale from one color and keeps text readable in both modes.",
  },
  {
    question: "How do I update a component?",
    answer:
      "Run the add command again. The CLI asks before it replaces a file, so you can look at the change first.",
  },
]

const footer = [
  {
    title: "Product",
    links: [
      { label: "Components", href: "/docs/components" },
      { label: "Foundations", href: "/docs/foundations" },
      { label: "Themes", href: "/themes" },
      { label: "Examples", href: "/examples" },
    ],
  },
  {
    title: "Resources",
    links: [
      { label: "Documentation", href: "/docs" },
      { label: "Installation", href: "/docs/installation" },
      { label: "Changelog", href: "/docs/resources/changelog" },
    ],
  },
]

/** One width and one gutter for every section. */
const container = "mx-auto w-full max-w-6xl px-4 @md:px-6"

function Logo() {
  return (
    <Link href="/" className="flex items-center gap-2 rounded-md focus-ring">
      <svg viewBox="0 0 24 24" fill="currentColor" className="size-6">
        <path d="M3 21V3a18 18 0 0 1 18 18z" />
      </svg>
      <span className="font-semibold tracking-tight">prfct</span>
    </Link>
  )
}

/** A command people copy rather than retype. */
function Command({ children }: { children: string }) {
  const [copied, setCopied] = React.useState(false)

  // The check mark confirms the copy for a moment, then turns back.
  React.useEffect(() => {
    if (!copied) return
    const timer = setTimeout(() => setCopied(false), 2000)
    return () => clearTimeout(timer)
  }, [copied])

  async function copy() {
    try {
      await navigator.clipboard.writeText(children)
      setCopied(true)
    } catch {
      // No clipboard access (an insecure page, a denied permission): nothing
      // was copied, so nothing is confirmed.
    }
  }

  return (
    <div className="flex max-w-full items-center gap-1 rounded-xl border bg-surface py-1 pr-1 pl-4">
      <code className="truncate font-mono text-sm">
        <span className="text-muted-foreground select-none">$ </span>
        {children}
      </code>
      <Button
        variant="ghost"
        size="icon-sm"
        aria-label={copied ? "Copied" : "Copy command"}
        onClick={copy}
      >
        {copied ? <CheckIcon /> : <CopyIcon />}
      </Button>
    </div>
  )
}

function SectionHeading({ title, text }: { title: string; text: string }) {
  return (
    <div className="flex max-w-2xl flex-col gap-3">
      <h2 className="text-heading-xl text-balance @2xl:text-display-sm">
        {title}
      </h2>
      <p className="text-body-lg text-pretty text-muted-foreground">{text}</p>
    </div>
  )
}

function Header() {
  return (
    <header className="border-b">
      <div className={cn(container, "flex h-14 items-center gap-8")}>
        <Logo />
        <nav aria-label="Sections" className="flex gap-6 @max-2xl:hidden">
          {navigation.map((item) => (
            <a
              key={item.href}
              href={item.href}
              className="rounded-md text-sm text-muted-foreground focus-ring transition-colors duration-fast hover:text-foreground"
            >
              {item.label}
            </a>
          ))}
        </nav>
        <Link
          href="/docs/installation"
          className={buttonVariants({ size: "sm", className: "ml-auto" })}
        >
          Get started
        </Link>
      </div>
    </header>
  )
}

function Hero() {
  return (
    <section className={cn(container, "pt-14 pb-12 @2xl:pt-24 @2xl:pb-16")}>
      <div className="mx-auto flex max-w-3xl flex-col items-center gap-6 text-center">
        <Link
          href="/examples"
          className="flex items-center gap-2 rounded-full border bg-card py-1 pr-3 pl-1 text-sm focus-ring transition-colors duration-fast hover:border-border-strong"
        >
          <Badge>New</Badge>
          Example apps you can install
          <ArrowRightIcon className="size-3.5" aria-hidden="true" />
        </Link>
        <h1 className="text-display-sm text-balance @2xl:text-display-lg @5xl:text-display-xl">
          A design system for Next.js, built on shadcn
        </h1>
        <p className="max-w-2xl text-body-lg text-pretty text-muted-foreground">
          Tokens with tested contrast, accessible components and complete
          example apps. You install them with the shadcn CLI, and the code is
          yours.
        </p>
        <div className="flex flex-wrap justify-center gap-3">
          <Link
            href="/docs/installation"
            className={buttonVariants({ size: "lg" })}
          >
            Get started
            <ArrowRightIcon data-icon="inline-end" />
          </Link>
          <Link
            href="/docs/components"
            className={buttonVariants({ variant: "outline", size: "lg" })}
          >
            Browse components
          </Link>
        </div>
        <Command>{install}</Command>
      </div>
    </section>
  )
}

function Components() {
  return (
    <section aria-label="Components" className={cn(container, "pb-20")}>
      {/* A picture of the system, not a form to fill in: nothing in it takes focus. */}
      <div inert className="rounded-2xl border bg-surface p-3 @2xl:p-4">
        <Showcase />
      </div>
      <p className="mt-4 text-center text-sm text-muted-foreground">
        Every element above is a prfct component in its default theme.
      </p>
    </section>
  )
}

function Features() {
  return (
    <section id="features" className="border-t">
      <div className={cn(container, "py-20 @2xl:py-28")}>
        <SectionHeading
          title="What you start with"
          text="The decisions a design system has to make, already made and written down."
        />
        <div className="mt-12 grid gap-x-10 gap-y-12 @2xl:grid-cols-2 @5xl:grid-cols-3">
          {features.map(({ icon: Icon, title, text }) => (
            <div key={title} className="flex flex-col gap-3">
              <span className="flex size-10 items-center justify-center rounded-lg border bg-card">
                <Icon className="size-5" aria-hidden="true" />
              </span>
              <h3 className="text-heading-sm">{title}</h3>
              <p className="text-sm leading-6 text-muted-foreground">{text}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  )
}

function Steps() {
  return (
    <section className="border-t bg-surface">
      <div className={cn(container, "py-20 @2xl:py-28")}>
        <SectionHeading
          title="From an empty folder to a first screen"
          text="No package to configure and nothing to wrap your app in."
        />
        <ol className="mt-12 grid gap-10 @5xl:grid-cols-3">
          {steps.map((step, index) => (
            <li key={step.title} className="flex min-w-0 flex-col gap-3">
              <span className="font-mono text-sm text-muted-foreground">
                0{index + 1}
              </span>
              <h3 className="text-heading-sm">{step.title}</h3>
              <p className="text-sm leading-6 text-muted-foreground">
                {step.text}
              </p>
              <Command>{step.command}</Command>
            </li>
          ))}
        </ol>
      </div>
    </section>
  )
}

function Examples() {
  return (
    <section id="examples" className="border-t">
      <div className={cn(container, "py-20 @2xl:py-28")}>
        <div className="flex flex-wrap items-end justify-between gap-6">
          <SectionHeading
            title="Start from a finished screen"
            text="Example apps written to be read, each installed with one command."
          />
          <Link
            href="/examples"
            className={buttonVariants({ variant: "outline" })}
          >
            All examples
            <ArrowRightIcon data-icon="inline-end" />
          </Link>
        </div>
        <ul className="mt-12 grid gap-x-10 border-t @2xl:grid-cols-2">
          {examples.map((example) => (
            <li key={example.href} className="border-b">
              <Link
                href={example.href}
                className="group flex items-center gap-4 rounded-md py-5 focus-ring"
              >
                <span className="flex min-w-0 flex-1 flex-col gap-1">
                  <span className="font-medium">{example.name}</span>
                  <span className="text-sm text-muted-foreground">
                    {example.text}
                  </span>
                </span>
                <ArrowRightIcon
                  className="size-4 text-muted-foreground transition-transform duration-fast group-hover:translate-x-0.5 group-hover:text-foreground"
                  aria-hidden="true"
                />
              </Link>
            </li>
          ))}
        </ul>
      </div>
    </section>
  )
}

function Questions() {
  return (
    <section id="questions" className="border-t">
      <div
        className={cn(
          container,
          "grid gap-10 py-20 @2xl:py-28 @5xl:grid-cols-[2fr_3fr]"
        )}
      >
        <SectionHeading
          title="Questions"
          text="The ones people ask before they install anything."
        />
        <Accordion defaultValue={[questions[0].question]}>
          {questions.map((item) => (
            <AccordionItem key={item.question} value={item.question}>
              <AccordionTrigger>{item.question}</AccordionTrigger>
              <AccordionContent>{item.answer}</AccordionContent>
            </AccordionItem>
          ))}
        </Accordion>
      </div>
    </section>
  )
}

/** Dark in both themes, so the page ends on its one loud moment. */
function Closing() {
  return (
    <section className="dark border-t bg-background text-foreground">
      <div
        className={cn(
          container,
          "flex flex-col items-center gap-6 py-20 text-center @2xl:py-28"
        )}
      >
        <h2 className="max-w-2xl text-heading-xl text-balance @2xl:text-display-md">
          Install the theme and build your first screen
        </h2>
        <Command>{install}</Command>
        <div className="flex flex-wrap justify-center gap-3">
          <Link
            href="/docs/installation"
            className={buttonVariants({ size: "lg" })}
          >
            Read the installation guide
          </Link>
          <Link
            href="/examples"
            className={buttonVariants({ variant: "outline", size: "lg" })}
          >
            See the examples
          </Link>
        </div>
      </div>
    </section>
  )
}

function Footer() {
  return (
    <footer className="border-t">
      <div
        className={cn(
          container,
          "grid gap-10 py-12 @2xl:grid-cols-[2fr_1fr_1fr]"
        )}
      >
        <div className="flex flex-col items-start gap-3">
          <Logo />
          <p className="max-w-xs text-sm text-muted-foreground">
            A design system for Next.js, built on shadcn. Released under the MIT
            license.
          </p>
        </div>
        {footer.map((group) => (
          <nav
            key={group.title}
            aria-label={group.title}
            className="flex flex-col gap-3"
          >
            <h2 className="text-sm font-medium">{group.title}</h2>
            <ul className="flex flex-col gap-2">
              {group.links.map((link) => (
                <li key={link.href}>
                  <Link
                    href={link.href}
                    className="rounded-md text-sm text-muted-foreground focus-ring transition-colors duration-fast hover:text-foreground"
                  >
                    {link.label}
                  </Link>
                </li>
              ))}
            </ul>
          </nav>
        ))}
      </div>
    </footer>
  )
}

/**
 * A landing page, top to bottom. `chrome` is its own header and footer:
 * leave them out where the page sits inside a site that has both, as it
 * does on prfct.dev.
 */
export default function LandingExample({
  chrome = true,
}: {
  chrome?: boolean
}) {
  return (
    <div className="@container bg-background text-foreground">
      {chrome && <Header />}
      <Hero />
      <Components />
      <Features />
      <Steps />
      <Examples />
      <Questions />
      <Closing />
      {chrome && <Footer />}
    </div>
  )
}
components/landing/landing-showcase.tsx
import {
  CircleCheckIcon,
  GitBranchIcon,
  MinusIcon,
  PlusIcon,
} from "lucide-react"

import aurora from "./aurora.svg"
import ember from "./ember.svg"
import tide from "./tide.svg"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldSeparator,
  FieldTitle,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSeparator,
  InputOTPSlot,
} from "@/components/ui/input-otp"
import { Progress } from "@/components/ui/progress"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Slider } from "@/components/ui/slider"
import { Switch } from "@/components/ui/switch"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

const people = [
  { name: "Maya Chen", role: "Owner", image: aurora.src, owner: true },
  { name: "Jonah Okafor", role: "Admin", image: ember.src },
  { name: "Lena Park", role: "Member", image: tide.src },
]

const plans = [
  { value: "hobby", title: "Hobby", description: "One project", price: "$0" },
  {
    value: "pro",
    title: "Pro",
    description: "Unlimited projects",
    price: "$20",
  },
]

const alerts = [
  { id: "deploys", title: "Deployments", on: true },
  { id: "comments", title: "Comments", on: true },
  { id: "digest", title: "Weekly digest", on: false },
]

function AccountCard() {
  return (
    <Card size="sm">
      <CardHeader>
        <CardTitle>Create an account</CardTitle>
        <CardDescription>Start with your work email.</CardDescription>
      </CardHeader>
      <CardContent>
        <FieldGroup>
          <Field>
            <FieldLabel htmlFor="showcase-name">Name</FieldLabel>
            <Input id="showcase-name" defaultValue="Ada Lovelace" />
          </Field>
          <Field>
            <FieldLabel htmlFor="showcase-email">Email</FieldLabel>
            <Input id="showcase-email" placeholder="ada@example.com" />
          </Field>
          <Field orientation="horizontal">
            <Checkbox id="showcase-updates" defaultChecked />
            <FieldLabel htmlFor="showcase-updates" className="font-normal">
              Send me product updates
            </FieldLabel>
          </Field>
          <Button>Create account</Button>
          <FieldSeparator>or</FieldSeparator>
          <Button variant="outline">
            <GitBranchIcon data-icon="inline-start" />
            Continue with Git
          </Button>
        </FieldGroup>
      </CardContent>
    </Card>
  )
}

export function TeamCard() {
  return (
    <Card size="sm">
      <CardHeader>
        <CardTitle>Team</CardTitle>
        <CardDescription>Three people can open this project.</CardDescription>
      </CardHeader>
      <CardContent className="flex flex-col gap-3">
        {people.map((person) => (
          <div key={person.name} className="flex items-center gap-3">
            <Avatar>
              <AvatarImage src={person.image} alt="" />
              <AvatarFallback>{person.name[0]}</AvatarFallback>
            </Avatar>
            <span className="flex-1 truncate text-sm font-medium">
              {person.name}
            </span>
            <Badge variant={person.owner ? "default" : "outline"}>
              {person.role}
            </Badge>
          </div>
        ))}
      </CardContent>
    </Card>
  )
}

export function PlanCard() {
  return (
    <Card size="sm">
      <CardHeader>
        <CardTitle>Plan</CardTitle>
        <CardDescription>Change it whenever you like.</CardDescription>
      </CardHeader>
      <CardContent>
        <RadioGroup defaultValue="pro" aria-label="Plan">
          {plans.map((plan) => (
            <FieldLabel key={plan.value} htmlFor={`showcase-${plan.value}`}>
              <Field orientation="horizontal">
                <RadioGroupItem
                  value={plan.value}
                  id={`showcase-${plan.value}`}
                />
                <FieldContent>
                  <FieldTitle>{plan.title}</FieldTitle>
                  <FieldDescription>{plan.description}</FieldDescription>
                </FieldContent>
                <span className="text-sm font-semibold tabular-nums">
                  {plan.price}
                </span>
              </Field>
            </FieldLabel>
          ))}
        </RadioGroup>
      </CardContent>
    </Card>
  )
}

export function NotificationsCard() {
  return (
    <Card size="sm">
      <CardHeader>
        <CardTitle>Notifications</CardTitle>
        <CardDescription>Changes save as you switch.</CardDescription>
      </CardHeader>
      <CardContent>
        <FieldGroup className="gap-3">
          {alerts.map((alert) => (
            <Field key={alert.id} orientation="horizontal">
              <FieldLabel
                htmlFor={`showcase-${alert.id}`}
                className="flex-1 font-normal"
              >
                {alert.title}
              </FieldLabel>
              <Switch id={`showcase-${alert.id}`} defaultChecked={alert.on} />
            </Field>
          ))}
        </FieldGroup>
      </CardContent>
    </Card>
  )
}

function UsageCard() {
  return (
    <Card size="sm">
      <CardHeader>
        <CardTitle className="flex items-center gap-2">
          Usage <Badge variant="success">On track</Badge>
        </CardTitle>
        <CardDescription>Resets on the first of the month.</CardDescription>
      </CardHeader>
      <CardContent className="flex flex-col gap-2">
        <div className="flex justify-between text-sm">
          <span>Build minutes</span>
          <span className="text-muted-foreground tabular-nums">
            3,120 of 6,000
          </span>
        </div>
        <Progress value={52} aria-label="Build minutes used" />
      </CardContent>
      <CardFooter className="justify-end">
        <Button variant="outline" size="sm">
          Manage plan
        </Button>
      </CardFooter>
    </Card>
  )
}

function AppearanceCard() {
  return (
    <Card size="sm">
      <CardHeader>
        <CardTitle>Appearance</CardTitle>
        <CardDescription>How the editor looks on this device.</CardDescription>
      </CardHeader>
      <CardContent className="flex flex-col gap-5">
        {/* One choice of three with nothing to switch between: a toggle
            group, not tabs. */}
        <ToggleGroup
          variant="outline"
          spacing={0}
          defaultValue={["system"]}
          aria-label="Theme"
          className="w-full"
        >
          <ToggleGroupItem value="light" className="flex-1">
            Light
          </ToggleGroupItem>
          <ToggleGroupItem value="dark" className="flex-1">
            Dark
          </ToggleGroupItem>
          <ToggleGroupItem value="system" className="flex-1">
            System
          </ToggleGroupItem>
        </ToggleGroup>
        <div className="flex items-center gap-3 text-muted-foreground">
          <MinusIcon className="size-4 shrink-0" aria-hidden="true" />
          <Slider defaultValue={60} aria-label="Text size" />
          <PlusIcon className="size-4 shrink-0" aria-hidden="true" />
        </div>
      </CardContent>
    </Card>
  )
}

function VerifyCard() {
  return (
    <Card size="sm">
      <CardHeader>
        <CardTitle>Two-step verification</CardTitle>
        <CardDescription>
          Enter the code from your authenticator.
        </CardDescription>
      </CardHeader>
      <CardContent className="flex flex-col items-start gap-4">
        <InputOTP maxLength={6} aria-label="Verification code">
          <InputOTPGroup>
            <InputOTPSlot index={0} />
            <InputOTPSlot index={1} />
            <InputOTPSlot index={2} />
          </InputOTPGroup>
          <InputOTPSeparator />
          <InputOTPGroup>
            <InputOTPSlot index={3} />
            <InputOTPSlot index={4} />
            <InputOTPSlot index={5} />
          </InputOTPGroup>
        </InputOTP>
        <Button size="sm">Verify</Button>
      </CardContent>
    </Card>
  )
}

const column = "flex flex-col gap-4"

/**
 * A wall of components in their default theme: what the system looks like
 * before anyone has styled anything. One column on a phone, three on a desk.
 */
export default function LandingShowcase() {
  return (
    <div className="grid gap-4 @2xl:grid-cols-2 @5xl:grid-cols-3">
      <div className={column}>
        <AccountCard />
        <UsageCard />
      </div>
      <div className={`${column} @max-2xl:hidden`}>
        <TeamCard />
        <PlanCard />
        <Alert variant="success">
          <CircleCheckIcon />
          <AlertTitle>Deployment ready</AlertTitle>
          <AlertDescription>
            Production now serves the build from main.
          </AlertDescription>
        </Alert>
      </div>
      <div className={`${column} @max-5xl:hidden`}>
        <NotificationsCard />
        <AppearanceCard />
        <VerifyCard />
      </div>
    </div>
  )
}
components/landing/aurora.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
  <rect width="80" height="80" fill="#e4eaff"/>
  <circle cx="40" cy="31" r="14" fill="#8ea6ff"/>
  <path d="M12 80c0-15.5 12.5-26 28-26s28 10.5 28 26z" fill="#8ea6ff"/>
</svg>
components/landing/ember.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
  <rect width="80" height="80" fill="#fdebe0"/>
  <circle cx="40" cy="31" r="14" fill="#f0a072"/>
  <path d="M12 80c0-15.5 12.5-26 28-26s28 10.5 28 26z" fill="#f0a072"/>
</svg>
components/landing/tide.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
  <rect width="80" height="80" fill="#dcf2ef"/>
  <circle cx="40" cy="31" r="14" fill="#6cbdb3"/>
  <path d="M12 80c0-15.5 12.5-26 28-26s28 10.5 28 26z" fill="#6cbdb3"/>
</svg>
app/landing/page.tsx
import type { Metadata } from "next"

import Landing from "@/components/landing/landing"

export const metadata: Metadata = {
  title: "Landing page: classic",
  description: "A centered hero with a wall of components, features with icons, install steps, a list of examples and questions.",
}

export default function LandingPage() {
  return <Landing />
}