# Hero 02 - Real Estate Hero Section

> Luxury real estate hero section crafted for property listings, featuring immersive imagery, location highlight, key amenities, pricing details, and prominent Schedule a tour CTA for faster inquiries.

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

- Page, with a live preview: https://shadcn.exchange/r/shadcn-space/hero-02
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcn-space/hero-02-light-abdb519104326cee.png
- Registry item: https://shadcnspace.com/r/hero-02.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnspace.com/r/hero-02.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`
- Registry items: `sheet`, `button`, `separator`

## Files

### `src/components/shadcn-space/blocks/hero-02/page.tsx`

```tsx
import Hero02Page from "@/components/shadcn-space/blocks/hero-02/index";

const Page = () => {
    return (
        <Hero02Page />
    )
}

export default Page
```

### `src/components/shadcn-space/blocks/hero-02/hero.tsx`

```tsx
"use client";

import { useRef } from "react";
import { Button } from "@/components/ui/button";
import SeamlessCloud from "@/components/shadcn-space/blocks/hero-02/seamless-cloud";
import { Bath, BedDouble, Car } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import { motion, useInView } from "motion/react";

const propertyFeatures = [
  {
    icon: BedDouble,
    label: "4 Bedrooms",
    className: "border-e border-b",
  },
  {
    icon: Bath,
    label: "4 Restroom",
    className: "border-b",
  },
  {
    icon: Car,
    label: "Parking space",
    className: "border-e",
  },
  {
    price: "$4,750,000",
    label: "For selling price",
    className: "",
  },
];

const HeroSection: React.FC = () => {
  const sectionRef = useRef<HTMLElement>(null);
  const isInView = useInView(sectionRef, { once: true, amount: 0.1 });

  return (
    <section ref={sectionRef}>
      <div className="bg-[url(https://images.shadcnspace.com/assets/backgrounds/real-estate-bg.webp)] bg-contain bg-center bg-repeat overflow-hidden relative flex flex-col xl:h-screen justify-center z-10 xl:gap-0 gap-12">
        <div className="max-w-7xl mx-auto sm:px-16 px-4 w-full xl:pt-0 pt-32">
          <div className="relative text-white text-start z-30">
            <p className="text-inherit text-xs font-normal">Palm springs, CA</p>
            <h1 className="text-inherit text-5xl! md:text-6xl! lg:text-7xl! font-normal! max-w-32 mt-2 mb-6">
              Space <span className="font-semibold!">Residence</span>
            </h1>
            <div>
              <Button className="px-6 py-3.5 bg-white border-0 text-black duration-300 hover:bg-white/80 font-medium rounded-full hover:cursor-pointer h-auto">
                <a href="#">Schedule a tour</a>
              </Button>
            </div>
          </div>
        </div>
        <div className="xl:absolute bottom-0 right-0 z-30 xl:w-auto lg:w-4/5 w-full lg:ms-auto">
          <div className="relative">
            <div className="xl:absolute bottom-24 w-full z-0">
              <img
                src={
                  "https://images.shadcnspace.com/assets/backgrounds/hero-4-banner.webp"
                }
                alt="heroImg"
                width={956}
                height={897}
                className="w-full"
              />
            </div>
            <div className="bg-background rounded-t-2xl xl:rounded-none xl:rounded-tl-2xl sm:py-10 py-6 sm:ps-12 ps-4 sm:pe-12 pe-4 xl:pe-60 z-1 relative">
              <motion.div
                initial={{ opacity: 0, y: 40 }}
                animate={
                  isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 40 }
                }
                transition={{ duration: 0.05, ease: "easeInOut" }}
                className="grid grid-cols-2 sm:grid-cols-4 gap-0 sm:flex sm:items-center justify-center sm:gap-10 sm:text-center"
              >
                {propertyFeatures.map((item, index) => (
                  <motion.div
                    key={index}
                    initial={{ opacity: 0, y: 20 }}
                    animate={
                      isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }
                    }
                    transition={{
                      duration: 0.05,
                      delay: 0.02 + index * 0.2,
                      ease: "easeInOut",
                    }}
                    className="flex sm:gap-10"
                  >
                    <div
                      className={`flex flex-col items-center gap-3 sm:py-0 sm:px-0 py-5 px-8 sm:border-0 border-gray-200 dark:border-gray-700 w-full ${item.className}`}
                    >
                      {item.icon ? (
                        <>
                          <item.icon
                            size={28}
                            className="text-foreground font-light"
                          />
                          <p className="text-sm font-normal text-muted-foreground">
                            {item.label}
                          </p>
                        </>
                      ) : (
                        <>
                          <p className="sm:text-xl text-lg font-semibold text-foreground">
                            {item.price}
                          </p>
                          <p className="text-sm font-normal text-muted-foreground">
                            {item.label}
                          </p>
                        </>
                      )}
                    </div>
                    {index < propertyFeatures.length - 1 && (
                      <Separator
                        orientation="vertical"
                        className="h-12 my-auto sm:block hidden"
                      />
                    )}
                  </motion.div>
                ))}
              </motion.div>
            </div>
          </div>
        </div>
        {/* Clouds */}
        <>
          <SeamlessCloud
            cloudCount={2}
            minSize={400}
            maxSize={678}
            opacity="opacity-60"
            gapMin={100}
            gapMax={500}
            top="top-56 sm:top-40 left-0"
          />
        </>
      </div>
    </section>
  );
};

export default HeroSection;
```

### `src/components/shadcn-space/blocks/hero-02/seamless-cloud.tsx`

```tsx
'use client';
import { Marquee } from '@/components/shadcn-space/animations/marquee';
import { useEffect, useState } from 'react';

export default function SeamlessCloud({
  top = "top-72 sm:top-20",
  cloudCount = 5,
  minSize = 400,
  maxSize = 678,
  opacity = "opacity-60",
  gapMin = 50,
  gapMax = 200,
}) {
  const [clouds, setClouds] = useState<{ width: number; gap: number }[]>([]);

  useEffect(() => {
    const generated = Array.from({ length: cloudCount }).map(() => ({
      width: Math.floor(Math.random() * (maxSize - minSize + 1)) + minSize,
      gap: Math.floor(Math.random() * (gapMax - gapMin + 1)) + gapMin,
    }));

    setClouds(generated);
  }, [cloudCount, minSize, maxSize, gapMin, gapMax]);

  if (clouds.length === 0) return null; // prevents mismatch

  return (
    <div
      className={`absolute ${top} inset-x-0 z-0 overflow-hidden pointer-events-none`}
    >
      <Marquee pauseOnHover className="[--duration:30s]">
        {clouds.map((cloud, i) => (
          <img
            key={i}
            src="https://images.shadcnspace.com/assets/backgrounds/cloud.webp"
            alt="cloud"
            width={cloud.width}
            height={350}
            className={opacity}
            style={{ marginRight: cloud.gap }}
          />
        ))}
      </Marquee>
    </div>
  );
}
```

### `src/components/shadcn-space/blocks/hero-02/navbar.tsx`

```tsx
"use client";

import React, { useEffect, useState, useCallback, useRef } from 'react'
import { cn } from '@/lib/utils';
import {
  Sheet,
  SheetContent,
  SheetClose,
  SheetHeader,
  SheetTitle,
} from '@/components/ui/sheet'
import { Button } from '@/components/ui/button'
import { Menu, Phone, X } from 'lucide-react'
import { Separator } from '@/components/ui/separator';
import { motion, useInView } from "motion/react";

type navData = {
  name: string;
  href: string;
};

type NavbarProps = {
  navData: navData[]
}

export type NavLinkItem = {
  name: string
  label?: string
  href: string
  isActive?: boolean
}

export interface NavLinkProps {
  item: NavLinkItem
}

const NavLink: React.FC<NavLinkProps> = ({
  item,
}) => {

  return (
    <li className="flex items-center group w-fit cursor-pointer">
      <div className={`h-0.5 bg-primary transition-all duration-300 ${item.isActive ? 'w-6 mr-4' : 'w-0 group-hover:w-6 group-hover:mr-4'}`} />
      <a
        href={item.href}
        className={`text-2xl font-medium rounded-full transition-colors ${item.isActive ? 'text-primary' : "text-muted-foreground group-hover:text-primary"}`}
      >
        {item.name}
      </a>
    </li>
  )
}

const Navbar: React.FC<NavbarProps> = ({ navData }) => {
  const [sticky, setSticky] = useState(false);
  const [navbarOpen, setNavbarOpen] = useState(false);
  const headerRef = useRef<HTMLElement>(null);
  const isInView = useInView(headerRef, { once: true, amount: 0.1 });

  const handleScroll = useCallback(() => {
    setSticky(window.scrollY >= 50);
  }, []);

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);

    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, [handleScroll]);

  return (
    <motion.header
      ref={headerRef}
      initial={{ opacity: 0, y: -32 }}
      animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: -32 }}
      transition={{ duration: 0.7, ease: "easeInOut" }}
      className={cn(
        "z-50 w-full bg-transparent h-20 sticky top-0 flex items-center"
      )}
    >
      <nav
        className={`max-w-7xl mx-auto sm:px-16 px-4 w-full`}>
        <div className={`flex items-center justify-between duration-300 ${sticky
          ? 'shadow-lg bg-background rounded-full p-3'
          : 'shadow-none px-0'
          }`}>
          <div className='flex justify-between items-center gap-2 w-full'>
            <div>
              <a href='#'>
                <img
                  src={
                    'https://images.shadcnspace.com/assets/logo/shadcnspace-logo-black.svg'
                  }
                  alt='logo'
                  width={150}
                  height={68}
                  className={`${sticky ? 'block dark:hidden' : 'hidden'}`}
                />
                <img
                  src={
                    'https://images.shadcnspace.com/assets/logo/shadcnspace-logo-white.svg'
                  }
                  alt='logo'
                  width={150}
                  height={68}
                  className={`${sticky ? 'hidden dark:block' : 'block'}`}
                />
              </a>
            </div>

            <div className='flex items-center gap-2 sm:gap-6'>
              <div className={`hidden md:block`}>
                <a
                  href='tel:+1212456789'
                  className={`text-sm text-inherit flex items-center gap-2 ${sticky
                    ? 'text-foreground hover:text-primary border-white/50 dark:border-white/50'
                    : 'text-white hover:text-white/80 border-white/50 dark:border-white/50'
                    }`}>
                  <Phone size={20} />
                  +1-212-456-789
                </a>
              </div>
              <Separator orientation="vertical" className={`h-5 my-auto sm:block hidden ${sticky ? "bg-white/50 dark:bg-white/50" : "bg-white/50 dark:bg-white/50"}`} />
              <div>
                <Button
                  size={"lg"}
                  onClick={() => setNavbarOpen(!navbarOpen)}
                  className={`flex items-center gap-3 px-5 py-2.5 rounded-full hover:cursor-pointer border text-sm font-medium h-auto ${sticky
                    ? 'text-dark dark:text-white bg-background dark:hover:text-white dark:hover:bg-background hover:text-dark hover:bg-white border-dark dark:border-white'
                    : 'text-black bg-white dark:text-dark hover:bg-transparent hover:text-white border-white'
                    }`}
                  aria-label='Toggle mobile menu'>
                  <Menu size={16} />
                  Menu
                </Button>
              </div>
            </div>
          </div>
        </div>
      </nav>

        <Sheet open={navbarOpen} onOpenChange={setNavbarOpen}>
          <SheetContent
            side="right"
            showCloseButton={false}
            className="bg-background p-6 overflow-auto no-scrollbar border-none flex flex-col gap-12"
          >
            {/* Header / Close Button */}
            <SheetHeader>
              <SheetTitle className='sr-only'>Navigation Menu</SheetTitle>
              <SheetClose className='absolute top-4 right-4 rounded-full dark:bg-white bg-black dark:text-black text-white p-2 cursor-pointer'>
                <X size={24} />
              </SheetClose>
            </SheetHeader>

            {/* Navigation */}
            <nav>
              <ul className='flex flex-col gap-4'>
                {navData?.map((item, index) => (
                  <SheetClose key={index} className="w-fit">
                    <NavLink item={item} />
                  </SheetClose>
                ))}
              </ul>
            </nav>

            {/* Contact & Socials */}
            <div className="flex flex-col gap-12 text-foreground">
              {/* Contact */}
              <div className="flex flex-col gap-1">
                <p className="text-lg font-normal text-muted-foreground">
                  Contact
                </p>
                <a href="#" className="text-base font-medium hover:text-primary">
                  hello@homely.com
                </a>
                <a href="#" className="text-base font-medium hover:text-primary">
                  +1-212-456-7890
                </a>
              </div>

              {/* Socials */}
              <div className="flex flex-col gap-1">
                <p className="text-lg font-normal text-muted-foreground">
                  Socials
                </p>
                <a href="#" className="text-base font-medium hover:text-primary">
                  X / Twitter
                </a>
                <a href="#" className="text-base font-medium hover:text-primary">
                  Facebook
                </a>
                <a href="#" className="text-base font-medium hover:text-primary">
                  Instagram
                </a>
              </div>
            </div>
          </SheetContent>
        </Sheet>
    </motion.header>
  );
};

export default Navbar
```

### `src/components/shadcn-space/animations/marquee.tsx`

```tsx
import { ComponentPropsWithoutRef } from "react";
 
import { cn } from "@/lib/utils";
 
interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
  className?: string;
  /**
   * Whether to reverse the animation direction
   * @default false
   */
  reverse?: boolean;
  /**
   * Whether to pause the animation on hover
   * @default false
   */
  pauseOnHover?: boolean;
  /**
   * Content to be displayed in the marquee
   */
  children: React.ReactNode;
  /**
   * Whether to animate vertically instead of horizontally
   * @default false
   */
  vertical?: boolean;
  /**
   * Number of times to repeat the content
   * @default 4
   */
  repeat?: number;
}
 
export function Marquee({
  className,
  reverse = false,
  pauseOnHover = false,
  children,
  vertical = false,
  repeat = 4,
  ...props
}: MarqueeProps) {
  return (
    <>
      <style>
        {`
          @keyframes marquee {
            from {
              transform: translateX(0);
            }
            to {
              transform: translateX(calc(-100% - var(--gap)));
            }
          }
 
          @keyframes marquee-vertical {
            from {
              transform: translateY(0);
            }
            to {
              transform: translateY(calc(-100% - var(--gap)));
            }
          }
 
          @keyframes scroll {
            to {
              transform: translate(calc(-50% - 0.5rem));
            }
          }
 
          .animate-marquee {
            animation: marquee var(--duration) linear infinite;
          }
 
          .animate-marquee-vertical {
            animation: marquee-vertical var(--duration) linear infinite;
          }
 
          .animate-reverse {
            animation-direction: reverse !important;
          }
 
          .pause-on-hover:hover .animate-marquee,
          .pause-on-hover:hover .animate-marquee-vertical {
            animation-play-state: paused !important;
          }
 
          .animate-scroll {
            animation: scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite;
          }
        `}
      </style>
      <div
        {...props}
        className={cn(
          "group flex gap-(--gap) overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
          {
            "flex-row": !vertical,
            "flex-col": vertical,
            "pause-on-hover": pauseOnHover,
          },
          className,
        )}
      >
        {Array(repeat)
          .fill(0)
          .map((_, i) => (
            <div
              key={i}
              className={cn("flex shrink-0 justify-around gap-(--gap)", {
                "animate-marquee flex-row": !vertical,
                "animate-marquee-vertical flex-col": vertical,
                "animate-reverse": reverse,
              })}
            >
              {children}
            </div>
          ))}
      </div>
    </>
  );
}
```

### `src/components/shadcn-space/blocks/hero-02/index.tsx`

```tsx
import HeroSection from "@/components/shadcn-space/blocks/hero-02/hero"
import Navbar from "@/components/shadcn-space/blocks/hero-02/navbar";
import { NavLinkItem } from "@/components/shadcn-space/blocks/hero-02/navbar";

const Hero02Page = () => {

    const navData:NavLinkItem[] = [
        { name: 'Home', href: '#',isActive: true },
        { name: 'Properties', href: '#' ,isActive: false},
        { name: 'Blog', href: '#' ,isActive: false},
        { name: 'Contact', href: '#' ,isActive: false},
        { name: 'Docs', href: '#' ,isActive: false},
    ]
    return (
        <>
            <Navbar navData={navData} />
            <main className="-mt-20">
                <HeroSection />
            </main>
        </>
    )
}

export default Hero02Page
```
