# Hero Section v12

> Sans headline above a split product card: Atelier brand-kit copy with default/outline pill CTAs, plus a 6-slide poster panel with crossfade zoom transitions and rounded-full icon pagination.

A shadcn/ui block from the [@uiception](https://uiception.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/hero-section-v12).

- Page, with a live preview: https://shadcn.exchange/r/uiception/hero-section-v12
- Screenshot: https://shadcn.exchange/shot/uiception/hero-section-v12/light.png
- Registry item: https://uiception.com/r/hero-section-v12.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/uiception/hero-section-v12.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `geist`, `motion`
- Registry items: `https://uiception.com/r/block-media.json`, `button`, `card`

## Files

### `registry/new-york/blocks/hero-section-v12/page.tsx`

```tsx
import HeroSectionV12 from "./components/hero-section-v12"

export default function Page() {
  return <HeroSectionV12 />
}
```

### `registry/new-york/blocks/hero-section-v12/components/hero-section-v12.tsx`

```tsx
import { createBlockImage } from "@/lib/block-media"

import { slideFiles } from "../lib/config"
import { HeroV12Carousel } from "./hero-v12-carousel"

const blockImage = createBlockImage("hero-section-v12")

const slides = slideFiles.map(({ file, alt }) => ({
  src: blockImage(file),
  alt,
}))

export default function HeroSectionV12() {
  return <HeroV12Carousel slides={slides} />
}
```

### `registry/new-york/blocks/hero-section-v12/components/hero-v12-carousel.tsx`

```tsx
"use client"

import { useEffect, useState } from "react"
import Image from "next/image"
import Link from "next/link"
import { GeistSans } from "geist/font/sans"
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
import { AnimatePresence, MotionConfig, motion } from "motion/react"

import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"

import {
  AUTOPLAY_MS,
  body,
  headline,
  primaryCta,
  secondaryCta,
  title,
} from "../lib/config"

export type HeroSlide = {
  src: string
  alt: string
}

const SLIDE_EASE = [0.22, 1, 0.36, 1] as const

export function HeroV12Carousel({ slides }: { slides: HeroSlide[] }) {
  const [index, setIndex] = useState(0)
  const count = slides.length
  const active = slides[index] ?? slides[0]

  useEffect(() => {
    if (count <= 1) return

    const timer = window.setTimeout(() => {
      setIndex((current) => (current + 1) % count)
    }, AUTOPLAY_MS)

    return () => {
      window.clearTimeout(timer)
    }
  }, [index, count])

  if (!active || count === 0) {
    return null
  }

  function goPrev() {
    setIndex((current) => (current - 1 + count) % count)
  }

  function goNext() {
    setIndex((current) => (current + 1) % count)
  }

  return (
    <MotionConfig reducedMotion="user">
      <section className="bg-background py-4 text-foreground md:py-6 lg:py-8">
        <div className="mx-auto max-w-6xl px-4">
          <h1
            className={`${GeistSans.className} max-w-[18ch] text-3xl tracking-tighter hyphens-none sm:text-4xl lg:text-5xl lg:leading-[1.15]`}
          >
            {headline}
          </h1>

          <Card className="mt-8 gap-0 overflow-hidden rounded-2xl bg-foreground py-0 text-background ring-0 sm:mt-10">
            <div className="grid lg:grid-cols-5 lg:items-stretch">
              <div className="flex flex-col justify-center p-8 sm:p-10 lg:col-span-2 lg:p-12">
                <h2 className="text-3xl font-medium leading-[1.15] tracking-[-0.03em] sm:text-4xl">
                  {title}
                </h2>
                <p className="mt-4 max-w-md text-[0.9375rem] leading-relaxed text-background/75 sm:text-base">
                  {body}
                </p>
                <div className="mt-8 flex flex-wrap gap-3">
                  <Button
                    asChild
                    variant="default"
                    size="lg"
                    className="rounded-full"
                  >
                    <Link href={primaryCta.href}>{primaryCta.label}</Link>
                  </Button>
                  <Button
                    asChild
                    variant="secondary"
                    size="lg"
                    className="rounded-full"
                  >
                    <Link href={secondaryCta.href}>{secondaryCta.label}</Link>
                  </Button>
                </div>
              </div>

              <div className="relative min-h-90 w-full overflow-hidden lg:col-span-3 sm:min-h-110 lg:min-h-140">
                <AnimatePresence initial={false}>
                  <motion.div
                    key={active.src}
                    className="absolute inset-0"
                    initial={{ opacity: 0, scale: 1.04 }}
                    animate={{ opacity: 1, scale: 1 }}
                    exit={{ opacity: 0, scale: 0.985 }}
                    transition={{
                      duration: 0.7,
                      ease: SLIDE_EASE,
                    }}
                  >
                    <Image
                      src={active.src}
                      unoptimized
                      alt=""
                      fill
                      aria-hidden
                      className="scale-125 object-cover object-center blur-2xl"
                      sizes="(max-width: 1024px) 100vw, 60vw"
                    />
                    <Image
                      src={active.src}
                      unoptimized
                      alt={active.alt}
                      fill
                      className="object-contain object-center"
                      sizes="(max-width: 1024px) 100vw, 60vw"
                      preload={index === 0}
                    />
                  </motion.div>
                </AnimatePresence>

                <div className="absolute inset-x-0 bottom-0 z-10 flex justify-end gap-2 p-5 sm:p-6">
                  <Button
                    type="button"
                    variant="default"
                    size="icon"
                    className="rounded-full bg-background text-foreground hover:bg-muted"
                    aria-label="Previous image"
                    onClick={goPrev}
                  >
                    <ChevronLeftIcon />
                  </Button>
                  <Button
                    type="button"
                    variant="default"
                    size="icon"
                    className="rounded-full bg-background text-foreground hover:bg-muted"
                    aria-label="Next image"
                    onClick={goNext}
                  >
                    <ChevronRightIcon />
                  </Button>
                </div>
              </div>
            </div>
          </Card>
        </div>
      </section>
    </MotionConfig>
  )
}
```

### `registry/new-york/blocks/hero-section-v12/lib/config.ts`

```ts
export type HeroSlideFile = {
  file: string
  alt: string
}

/** Milliseconds between autoplay slide advances. */
export const AUTOPLAY_MS = 5000

export const headline = "Brand systems that look intentional on every surface."

export const title = "Acme Corp. for product teams"

export const body =
  "Keep posters, packaging comps, and launch decks in one shared kit. Swap art direction once and every asset stays on-brand without rebuilding the deck from scratch."

export const primaryCta = {
  label: "Browse kits",
  href: "#",
} as const

export const secondaryCta = {
  label: "Book a walkthrough",
  href: "#",
} as const

export const slideFiles: HeroSlideFile[] = [
  {
    file: "image-1.jpg",
    alt: "Bakery poster stacking four filled doughnuts beneath bold 'Dusted Filled' display type",
  },
  {
    file: "image-2.jpg",
    alt: "Night Owl cold brew bottle poster set against bold 'Awake' display type",
  },
  {
    file: "image-3.jpg",
    alt: "Reissued Leica M6 film camera poster titled 'Grain' on a grayscale gradient",
  },
  {
    file: "image-4.jpg",
    alt: "Mechanical keyboard poster titled 'Clack' on a cool gray backdrop",
  },
  {
    file: "image-5.jpg",
    alt: "Rose gold safety razor poster titled 'Smooth' over layered serif type",
  },
  {
    file: "image-6.jpg",
    alt: "Lavender sleep mist bottle poster titled 'Exhale' on a lilac background",
  },
]
```

### `public/images/blocks/hero-section-v12/image-1.jpg.gitkeep`

```gitkeep
Place your asset here. Same base name, any supported format: jpg, jpeg, png, webp, avif, gif, or svg. Remove the .gitkeep extension from the filename.
```

### `public/images/blocks/hero-section-v12/image-2.jpg.gitkeep`

```gitkeep
Place your asset here. Same base name, any supported format: jpg, jpeg, png, webp, avif, gif, or svg. Remove the .gitkeep extension from the filename.
```

### `public/images/blocks/hero-section-v12/image-3.jpg.gitkeep`

```gitkeep
Place your asset here. Same base name, any supported format: jpg, jpeg, png, webp, avif, gif, or svg. Remove the .gitkeep extension from the filename.
```

### `public/images/blocks/hero-section-v12/image-4.jpg.gitkeep`

```gitkeep
Place your asset here. Same base name, any supported format: jpg, jpeg, png, webp, avif, gif, or svg. Remove the .gitkeep extension from the filename.
```

### `public/images/blocks/hero-section-v12/image-5.jpg.gitkeep`

```gitkeep
Place your asset here. Same base name, any supported format: jpg, jpeg, png, webp, avif, gif, or svg. Remove the .gitkeep extension from the filename.
```

### `public/images/blocks/hero-section-v12/image-6.jpg.gitkeep`

```gitkeep
Place your asset here. Same base name, any supported format: jpg, jpeg, png, webp, avif, gif, or svg. Remove the .gitkeep extension from the filename.
```
