# Hero 3 - Split hero with review strip and tall photography

> A two-column hero using shadcn/ui Avatar and Button rows where overlapped faces, star score, and review total sit beside a tall rounded cover image.

A shadcn/ui block from the [@shadcnblocks](https://shadcnblocks.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcnblocks/hero3).

- Page, with a live preview: https://shadcn.exchange/r/shadcnblocks/hero3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcnblocks/hero3-light-959edeeaa8903064.png
- Registry item: https://shadcnblocks.com/r/hero3.json
- Listed: 2026-10-13, source last changed 2026-10-20

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/shadcnblocks/hero3.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: `cn`, `lucide-react`
- Registry items: `avatar`, `button`

## Files

### `block/hero3/hero3.tsx`

```tsx
import { ArrowRight, Star } from "lucide-react";
import { cn } from "cn";

import { Avatar, AvatarImage } from "@/components/ui/avatar";

import { Button } from "@/components/ui/button";

interface Image {
  src: string;
  alt: string;
  srcDark?: string;
}
interface Button {
  text: string;
  url: string;
  icon?: React.ReactNode;
}
interface Buttons {
  primary?: Button;
  secondary?: Button;
}

interface HeroBasicProps {
  heading: string;
  description: string;
  buttons?: Buttons;
  image: Image;
  className?: string;
}

interface Hero3Props extends HeroBasicProps {
  reviews?: Hero3Reviews;
}
type Props = Partial<Hero3Props>;

const person1 = {
  name: "Jack Walsh",
  firstName: "Jack",
  lastName: "Walsh",
  initials: "JW",
  email: "jack.walsh@example.com",
  username: "jackwalsh",
  role: "Product Designer",
  company: "Cartwright",
  location: "Brisbane, Australia",
  avatar: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/avatars/avatar1.webp",
};
const person2 = {
  name: "Ilze Pērkone",
  firstName: "Ilze",
  lastName: "Pērkone",
  initials: "MK",
  email: "iperkone@example.com",
  username: "iperkone",
  role: "Brand Director",
  company: "Kivit",
  location: "Rīga, Latvia",
  avatar: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/avatars/avatar2.webp",
};
const person3 = {
  name: "Vernon Desilets",
  firstName: "Vernon",
  lastName: "Desilets",
  initials: "VD",
  email: "vernod@example.com",
  username: "vernod",
  role: "Creative Director",
  company: "Soho Logix",
  location: "Paris, France",
  avatar: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/avatars/avatar3.webp",
};
const person4 = {
  name: "Lín Wàngshū",
  firstName: "Lín",
  lastName: "Wangshu",
  initials: "LW",
  email: "linwang4@example.com",
  username: "liwang",
  role: "Graphic Designer",
  company: "Tenxcloud",
  location: "Shanghai, China",
  avatar: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/avatars/avatar4.webp",
};
const person5 = {
  name: "Rajesh Bhatt",
  firstName: "Rajesh",
  lastName: "Bhatt",
  initials: "RB",
  email: "rajesh.bhatt@example.com",
  username: "rajeshbhatt",
  role: "UX Researcher",
  company: "Mindtree",
  location: "Mumbai, India",
  avatar: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/avatars/avatar5.webp",
};
const defaultProps: Hero3Props = {
  heading: "The CRM platform for modern product teams.",
  description: "An open-source project management platform built for modern product teams. Plan, track, and ship with confidence.",
  buttons: {
    primary: { text: "Get started", url: "#" },
    secondary: { text: "Learn more", url: "#" },
  },
  image: {
    src: "https://cdn.shadcnblocks.com/shadcnblocks/image-set/modern/saas-details/saas-detail-1-16x9.png",
    alt: "Product dashboard",
  },
  reviews: {
    count: 200,
    rating: 5.0,
    avatars: [person1, person2, person3, person4, person5].map((person) => ({
      src: person.avatar,
      alt: person.name,
    })),
  },
};

const MAX_REVIEW_AVATARS = 5;

interface Hero3Reviews {
  count: number;
  rating: number;
  avatars: { src: string; alt: string }[];
}

const Hero3 = (props: Props) => {
  const { heading, description, buttons, reviews, image, className } = {
    ...defaultProps,
    ...props,
  };

  const reviewAvatars = (reviews?.avatars ?? []).slice(0, MAX_REVIEW_AVATARS);

  return (
    <section className={cn("py-32", className)}>
      <div className="container mx-auto">
        <div className="grid items-center gap-10 lg:grid-cols-2 lg:gap-20">
          <div className="mx-auto flex max-w-5xl flex-col items-center gap-6 text-center md:ml-auto lg:order-1 lg:items-start lg:text-left">
            <h1 className="max-w-xl text-4xl font-bold tracking-tight text-pretty lg:max-w-3xl lg:text-6xl xl:text-7xl">
              {heading}
            </h1>
            <p className="max-w-5xl text-balance text-muted-foreground lg:text-xl">
              {description}
            </p>
            {reviews && (
              <div className="flex w-fit flex-col items-center gap-4 sm:flex-row">
                <span className="inline-flex items-center -space-x-3">
                  {reviewAvatars.map((avatar, index) => (
                    <Avatar
                      key={index}
                      className="size-10 bg-background ring-2 ring-background after:hidden"
                    >
                      <AvatarImage src={avatar.src} alt={avatar.alt} />
                    </Avatar>
                  ))}
                </span>
                <div>
                  <div className="flex items-center gap-1">
                    {[...Array(5)].map((_, index) => (
                      <Star
                        key={index}
                        className="size-5 fill-none stroke-amber-500 dark:stroke-amber-400"
                      />
                    ))}
                    <span className="ml-1 font-semibold text-foreground">
                      {reviews.rating.toFixed(1)}
                    </span>
                  </div>
                  <p className="text-left font-medium text-muted-foreground">
                    from {reviews.count}+ reviews
                  </p>
                </div>
              </div>
            )}
            <div className="flex w-full flex-col justify-center gap-2 sm:flex-row lg:justify-start">
              {buttons?.primary && (
                <Button asChild size="lg" className="w-full sm:w-auto">
                  <a href={buttons.primary.url}>
                    {buttons.primary.text}
                    <ArrowRight className="size-4" />
                  </a>
                </Button>
              )}
              {buttons?.secondary && (
                <Button
                  asChild
                  variant="outline"
                  size="lg"
                  className="w-full bg-background sm:w-auto dark:bg-background"
                >
                  <a href={buttons.secondary.url}>{buttons.secondary.text}</a>
                </Button>
              )}
            </div>
          </div>
          <div className="flex w-full lg:order-2">
            {image.srcDark ? (
              <>
                <img
                  src={image.src}
                  alt={image.alt}
                  className="aspect-video max-h-[600px] w-full rounded-md border border-border object-cover object-top lg:max-h-[800px] dark:hidden"
                />
                <img
                  src={image.srcDark}
                  alt={image.alt}
                  className="hidden aspect-video max-h-[600px] w-full rounded-md border border-border object-cover object-top lg:max-h-[800px] dark:block"
                />
              </>
            ) : (
              <img
                src={image.src}
                alt={image.alt}
                className="aspect-video max-h-[600px] w-full rounded-md border border-border object-cover object-top lg:max-h-[800px]"
              />
            )}
          </div>
        </div>
      </div>
    </section>
  );
};

export { Hero3 };
```
