# Hero 09

> A centered real-estate hero with a search bar, a masked architecture image that fades into the page, and a split closing statement.

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

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

## Install

```bash
npx shadcn@latest add https://ui.flexnative.com/r/hero-09.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: `input`, `button`

## Files

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

```tsx
'use client'

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

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

export interface Hero09Props {
  title: string
  titleLine2?: string
  description: string
  searchPlaceholder?: string
  searchButtonText?: string
  heroImage: string
  heroAlt?: string
  bottomTitle: string
  bottomTitleLine2?: string
  bottomText?: 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-md text-sm sm:text-base',
    header: 'gap-6',
    content: 'gap-10 sm:gap-14',
    media: 'max-w-4xl',
    imageAspect: 'aspect-16/10',
    bottomTitle: 'text-2xl sm:text-3xl md:text-4xl',
    overlap: 'mt-6',
  },
  compact: {
    section: 'py-14 sm:py-20',
    title: 'text-2xl sm:text-3xl md:text-4xl',
    description: 'max-w-sm text-sm',
    header: 'gap-4',
    content: 'gap-8 sm:gap-10',
    media: 'max-w-3xl',
    imageAspect: 'aspect-16/11',
    bottomTitle: 'text-xl sm:text-2xl md:text-3xl',
    overlap: 'mt-4',
  },
} 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: [0.22, 1, 0.36, 1] },
  },
}

const mediaItem: Variants = {
  hidden: { opacity: 0, y: 24, filter: 'blur(8px)' },
  visible: {
    opacity: 1,
    y: 0,
    filter: 'blur(0px)',
    transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
  },
}

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 Hero09({
  title,
  titleLine2,
  description,
  searchPlaceholder = 'Search blocks and components',
  searchButtonText = 'Search',
  heroImage,
  heroAlt = '',
  bottomTitle,
  bottomTitleLine2,
  bottomText,
  animation = 'none',
  variant = 'standard',
}: Readonly<Hero09Props>) {
  const reduce = useReducedMotion()
  const animate = animation === 'subtle' && !reduce
  const vs = variantStyles[variant]

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

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

  const searchElement = (
    <form
      onSubmit={(e) => e.preventDefault()}
      className="bg-card focus-within:ring-ring/40 mx-auto flex w-full max-w-lg items-center gap-2 rounded-full border p-1.5 shadow-sm transition focus-within:ring-2"
    >
      <div className="text-muted-foreground pl-3">
        <Search className="size-4" />
      </div>
      <Input
        aria-label={searchPlaceholder}
        placeholder={searchPlaceholder}
        className="h-9 flex-1 border-0 bg-transparent p-0 shadow-none focus-visible:ring-0 dark:bg-transparent"
      />
      <Button type="submit" className="shrink-0 rounded-full px-5">
        {searchButtonText}
      </Button>
    </form>
  )

  const mediaElement = heroImage && (
    <div className={cn('relative mx-auto w-full', vs.media)}>
      <div
        className={cn(
          'relative mx-auto w-full overflow-hidden',
          'mask-x-from-80% mask-x-to-100%',
          'mask-t-from-35% mask-t-to-95%',
          'mask-b-from-55% mask-b-to-100%',
          'mask-radial-[95%_85%] mask-radial-from-60% mask-radial-to-100% mask-radial-at-center',
          'dark:opacity-85',
        )}
      >
        <img
          src={heroImage}
          alt={heroAlt}
          decoding="async"
          className={cn('w-full object-cover object-center', vs.imageAspect)}
        />
      </div>
    </div>
  )

  const bottomElement = (bottomTitle || bottomText) && (
    <div
      className={cn(
        'grid grid-cols-1 items-end gap-6 lg:grid-cols-2',
        vs.overlap,
      )}
    >
      {bottomTitle && (
        <h2
          className={cn(
            'text-foreground font-serif font-normal tracking-tight text-balance',
            vs.bottomTitle,
          )}
        >
          <Balancer>{bottomTitle}</Balancer>
          {bottomTitleLine2 && (
            <>
              <br />
              <Balancer>{bottomTitleLine2}</Balancer>
            </>
          )}
        </h2>
      )}
      {bottomText && (
        <p className="text-muted-foreground max-w-xs text-sm lg:justify-self-end">
          <Balancer>{bottomText}</Balancer>
        </p>
      )}
    </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 px-6',
          vs.section,
          vs.content,
        )}
        variants={animate ? container : undefined}
        initial={animate ? 'hidden' : false}
        whileInView={animate ? 'visible' : undefined}
        viewport={{ once: true, margin: '-80px' }}
      >
        <Reveal
          active={animate}
          className={cn(
            'mx-auto flex w-full max-w-2xl flex-col items-center text-center',
            vs.header,
          )}
        >
          {titleElement}
          {descriptionElement}
          {searchElement}
        </Reveal>

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

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

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

```tsx
import { Hero09, type Hero09Props } from './hero-09'

export const values = {
  title: 'Find the perfect',
  titleLine2: 'block for your app.',
  description:
    'Production-ready UI blocks built with React, Tailwind, and shadcn.',
  searchPlaceholder: 'Search blocks and components',
  searchButtonText: 'Search',
  heroImage:
    'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?q=80&w=1400&auto=format&fit=crop',
  heroAlt: 'Modern minimalist house with clean architecture',
  bottomTitle: 'Blocks',
  bottomTitleLine2: 'crafted with purpose.',
  bottomText:
    'A curated library where clean design, accessibility, and copy-paste code come together.',
  animation: 'subtle',
} satisfies Hero09Props

export function Hero09Example() {
  return <Hero09 {...values} />
}
```
