# Hero 14

> A centered product hero with dual CTAs, a full-width photo with a floating block-builder demo panel, and a logo strip.

A shadcn/ui block from the [@flx](https://ui.flexnative.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/flx/hero-14).

- Page, with a live preview: https://shadcn.exchange/r/flx/hero-14
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/flx/hero-14-light-bdf87e090cd9c9c8.png
- Registry item: https://ui.flexnative.com/r/hero-14.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.flexnative.com/r/hero-14.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: `lucide-react`, `motion`, `react-wrap-balancer`
- Registry items: `@flx/cta`, `button`

## Files

### `registry/blocks/hero/hero-14/hero-14.tsx`

```tsx
'use client'

import * as React from 'react'
import { motion, useReducedMotion, type Variants } from 'motion/react'
import Balancer from 'react-wrap-balancer'

import { cn } from '@/lib/utils'

import { Cta, type CtaProps } from '../../shared/cta'
import { BlockBuilderDemo } from './block-builder-demo'

export interface Hero14Props {
  title: string
  description: string
  primaryCTA: CtaProps
  secondaryCTA?: CtaProps
  image: string
  imageAlt?: string
  logos?: string[]
  animation?: 'none' | 'subtle'
  variant?: 'standard' | 'compact'
}

const variantStyles = {
  standard: {
    section: 'py-20 sm:py-28',
    title: 'text-3xl sm:text-4xl md:text-5xl',
    description: 'max-w-xl text-sm sm:text-base',
    header: 'gap-3 sm:gap-4',
    copy: 'gap-5 sm:gap-6',
    content: 'gap-10 sm:gap-14',
    mediaPadding: 'px-6 py-10 sm:px-12 sm:py-16',
    logos: 'gap-x-10 gap-y-4 pt-8 sm:pt-10',
  },
  compact: {
    section: 'py-14 sm:py-20',
    title: 'text-2xl sm:text-3xl md:text-4xl',
    description: 'max-w-lg text-sm',
    header: 'gap-2.5 sm:gap-3',
    copy: 'gap-4 sm:gap-5',
    content: 'gap-8 sm:gap-10',
    mediaPadding: 'px-5 py-8 sm:px-10 sm:py-12',
    logos: 'gap-x-8 gap-y-3 pt-6 sm:pt-8',
  },
} as const

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

const container: Variants = {
  hidden: {},
  visible: { transition: { staggerChildren: 0.1, delayChildren: 0.05 } },
}

const item: Variants = {
  hidden: { opacity: 0, y: 12, filter: 'blur(6px)' },
  visible: {
    opacity: 1,
    y: 0,
    filter: 'blur(0px)',
    transition: { duration: 0.5, ease: easeOut },
  },
}

const mediaItem: Variants = {
  hidden: { opacity: 0, y: 24, filter: 'blur(8px)' },
  visible: {
    opacity: 1,
    y: 0,
    filter: 'blur(0px)',
    transition: { duration: 0.6, delay: 0.15, ease: easeOut },
  },
}

const logosItem: Variants = {
  hidden: { opacity: 0, y: 8 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.5, delay: 0.35, ease: easeOut },
  },
}

function Reveal({
  active,
  variants,
  className,
  children,
}: Readonly<{
  active: boolean
  variants?: Variants
  className?: string
  children: React.ReactNode
}>) {
  if (!active) return <div className={className}>{children}</div>

  return (
    <motion.div variants={variants ?? item} className={className}>
      {children}
    </motion.div>
  )
}

export function Hero14({
  title,
  description,
  primaryCTA,
  secondaryCTA,
  image,
  imageAlt = '',
  logos = [],
  animation = 'none',
  variant = 'standard',
}: Readonly<Hero14Props>) {
  const reduce = useReducedMotion()
  const animate = animation === 'subtle' && !reduce
  const vs = variantStyles[variant]

  const titleElement = title && (
    <h1
      className={cn(
        'text-foreground font-semibold tracking-tight text-balance',
        vs.title,
      )}
    >
      <Balancer>{title}</Balancer>
    </h1>
  )

  const descriptionElement = description && (
    <p className={cn('text-muted-foreground', vs.description)}>
      <Balancer>{description}</Balancer>
    </p>
  )

  const ctasElement = (primaryCTA?.ctaEnabled || secondaryCTA?.ctaEnabled) && (
    <div className="flex flex-wrap items-center justify-center gap-3">
      {secondaryCTA?.ctaEnabled && (
        <Cta
          cta={{ ...secondaryCTA, variant: secondaryCTA.variant ?? 'outline' }}
        />
      )}
      {primaryCTA?.ctaEnabled && <Cta cta={primaryCTA} />}
    </div>
  )

  const mediaElement = image && (
    <div className="relative w-full overflow-hidden rounded-2xl outline outline-black/10 dark:outline-white/10">
      <img
        src={image}
        alt={imageAlt}
        decoding="async"
        className="absolute inset-0 size-full object-cover"
      />
      <div
        aria-hidden
        className="absolute inset-0 bg-gradient-to-t from-black/15 via-transparent to-black/5"
      />
      <div
        className={cn(
          'relative flex items-center justify-center',
          vs.mediaPadding,
        )}
      >
        <BlockBuilderDemo />
      </div>
    </div>
  )

  const logosElement = logos.length > 0 && (
    <div
      className={cn(
        'flex w-full flex-wrap items-center justify-center border-t',
        vs.logos,
      )}
    >
      {logos.map((logo, index) => (
        <span
          key={`${logo}-${index}`}
          className="text-muted-foreground/70 text-sm font-semibold tracking-tight"
        >
          {logo}
        </span>
      ))}
    </div>
  )

  return (
    <section className="bg-background relative isolate w-full overflow-hidden">
      <motion.div
        className={cn(
          'relative z-10 mx-auto flex max-w-6xl flex-col items-center px-6',
          vs.section,
          vs.content,
        )}
        variants={animate ? container : undefined}
        initial={animate ? 'hidden' : false}
        whileInView={animate ? 'visible' : undefined}
        viewport={{ once: true, margin: '-80px' }}
      >
        <div
          className={cn(
            'flex w-full max-w-2xl flex-col items-center',
            vs.copy,
          )}
        >
          <Reveal
            active={animate}
            className={cn(
              'flex flex-col items-center text-center',
              vs.header,
            )}
          >
            {titleElement}
            {descriptionElement}
          </Reveal>

          <Reveal active={animate}>{ctasElement}</Reveal>
        </div>

        <Reveal active={animate} variants={mediaItem} className="w-full">
          {mediaElement}
        </Reveal>

        <Reveal active={animate} variants={logosItem} className="w-full">
          {logosElement}
        </Reveal>
      </motion.div>
    </section>
  )
}
```

### `registry/blocks/hero/hero-14/hero-14-example.tsx`

```tsx
import { Hero14, type Hero14Props } from './hero-14'

export const values = {
  title: 'Describe the screen. Ship the block.',
  description:
    'Turn a plain-language brief into a production-ready block you can copy into your app.',
  image:
    'https://images.unsplash.com/photo-1673271044466-b23ea152130f?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
  imageAlt: 'A pastel wildflower meadow at golden hour',
  logos: ['Northwind', 'Vertex Labs', 'Solstice', 'Anchorpoint', 'Lumen'],
  animation: 'subtle',
  variant: 'standard',
  primaryCTA: {
    ctaEnabled: true,
    text: 'Browse Blocks',
    link: '',
    size: 'default',
  },
  secondaryCTA: {
    ctaEnabled: true,
    text: 'How It Works',
    link: '',
    variant: 'outline',
    size: 'default',
  },
} satisfies Hero14Props

export function Hero14Example() {
  return <Hero14 {...values} />
}
```

### `registry/blocks/hero/hero-14/block-builder-demo.tsx`

```tsx
import { CheckCircle2, Send, Sparkles } from 'lucide-react'

import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'

const checklist = ['Three-tier layout', 'Monthly / yearly toggle']

export function BlockBuilderDemo() {
  return (
    <div
      className={cn(
        'bg-card w-full max-w-md overflow-hidden rounded-xl border',
        'shadow-[0_20px_50px_-16px_rgba(0,0,0,0.25)]',
        'dark:shadow-[0_20px_50px_-16px_rgba(0,0,0,0.6)]',
      )}
    >
      <div className="border-b px-5 py-3">
        <span className="text-muted-foreground text-[11px] font-medium tracking-wide uppercase">
          Block builder
        </span>
      </div>

      <div className="flex flex-col gap-3.5 px-5 py-5">
        <div className="flex items-end gap-2.5">
          <div className="bg-foreground text-background flex size-7 shrink-0 items-center justify-center rounded-md">
            <Sparkles className="size-4" />
          </div>
          <div className="bg-muted max-w-[80%] rounded-xl rounded-bl-sm px-3.5 py-2.5 text-[13px] leading-relaxed">
            Hi! Describe the block you want to build.
          </div>
        </div>

        <div className="flex justify-end">
          <div className="bg-foreground text-background max-w-[80%] rounded-xl rounded-br-sm px-3.5 py-2.5 text-[13px] leading-relaxed">
            A pricing section with three tiers
          </div>
        </div>

        <div className="bg-muted/60 space-y-2 rounded-xl p-3.5">
          <span className="text-sm font-medium tracking-tight">
            Pricing-03 block
          </span>
          <ul className="text-muted-foreground space-y-1 text-xs leading-relaxed">
            {checklist.map((entry) => (
              <li key={entry}>{entry}</li>
            ))}
          </ul>
          <div className="text-foreground flex items-center gap-1.5 pt-0.5 text-xs font-medium">
            <CheckCircle2 className="size-3.5 text-emerald-600 dark:text-emerald-400" />
            Ready to copy
          </div>
        </div>
      </div>

      <div className="border-t p-3">
        <div className="bg-muted/50 flex items-center gap-2 rounded-full py-2 pr-1.5 pl-4">
          <span className="text-muted-foreground min-w-0 flex-1 truncate text-[13px]">
            Describe your block…
          </span>
          <Button
            size="icon-xs"
            variant="default"
            className="size-8 shrink-0 rounded-full active:scale-[0.96]"
            aria-label="Send"
          >
            <Send className="size-4" />
          </Button>
        </div>
      </div>
    </div>
  )
}
```
