# Hero Split Reveal Block

> A Hero Split Reveal Block component for displaying a hero section.

A shadcn/ui block from the [@devsloka](https://ui.devsloka.in/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/devsloka/hero-split-reveal).

- Page, with a live preview: https://shadcn.exchange/r/devsloka/hero-split-reveal
- Screenshot: https://shadcn.exchange/shot/devsloka/hero-split-reveal/light.png
- Registry item: https://ui.devsloka.in/r/hero-split-reveal.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/devsloka/hero-split-reveal.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: `motion`
- Registry items: `button`

## Files

### `src/registry/blocks/hero-split-reveal.tsx`

```tsx
"use client";

import { useId, type ComponentProps } from "react";
import { motion, useReducedMotion } from "motion/react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

type Action = { label: string; href: string };

export type HeroSplitRevealProps = {
  eyebrow?: string;
  title?: string;
  description?: string;
  primaryAction?: Action;
  secondaryAction?: Action;
  /** Short facts in a mono list, e.g. delivery details. */
  facts?: string[];
  image?: { src: string; alt: string; caption?: string; credit?: string };
} & ComponentProps<"section">;

const DEFAULTS = {
  eyebrow: "Bramble · Autumn list, 2026",
  title: "Plants from people who grew them.",
  description:
    "We buy from fourteen small nurseries within a day's drive of our shed, then pack every pot by hand. No warehouse, no plastic wrap.",
  primaryAction: { label: "Browse the autumn list", href: "#" },
  secondaryAction: { label: "Meet the growers", href: "#" },
  facts: ["Delivered Tue–Fri", "Free over €45", "30-day thriving promise"],
  image: {
    src: "https://images.unsplash.com/photo-1485955900006-10f4d324d411?w=1200&q=80",
    alt: "A zebra haworthia in a mint-green pot against a white wall",
    caption: "Zebra haworthia, repotted this morning",
    credit: "Photographed in the packing shed",
  },
};

const EASE = [0.65, 0, 0.35, 1] as const;

export default function HeroSplitReveal({
  eyebrow = DEFAULTS.eyebrow,
  title = DEFAULTS.title,
  description = DEFAULTS.description,
  primaryAction = DEFAULTS.primaryAction,
  secondaryAction = DEFAULTS.secondaryAction,
  facts = DEFAULTS.facts,
  image = DEFAULTS.image,
  className,
  ...props
}: HeroSplitRevealProps) {
  const headingId = useId();
  const reduceMotion = useReducedMotion();

  return (
    <section
      data-slot="hero-split-reveal"
      aria-labelledby={headingId}
      className={cn("w-full bg-background text-foreground", className)}
      {...props}
    >
      <div className="mx-auto grid max-w-6xl items-end gap-12 px-6 py-20 md:grid-cols-12 md:gap-10 md:py-28">
        <div className="md:col-span-5 md:pb-4">
          <p className="font-mono text-xs uppercase tracking-wider text-muted-foreground">
            {eyebrow}
          </p>
          <h1
            id={headingId}
            className="mt-8 text-5xl font-semibold leading-[0.95] tracking-[-0.045em] text-balance md:text-6xl lg:text-7xl"
          >
            {title}
          </h1>
          <p className="mt-8 max-w-sm text-base leading-relaxed text-muted-foreground">
            {description}
          </p>
          <div className="mt-10 flex flex-wrap items-center gap-x-6 gap-y-3">
            <Button asChild size="lg" className="rounded-none">
              <a href={primaryAction.href}>{primaryAction.label}</a>
            </Button>
            <a
              href={secondaryAction.href}
              className="text-sm font-medium underline decoration-1 underline-offset-[6px] hover:text-muted-foreground"
            >
              {secondaryAction.label}
            </a>
          </div>
          {facts.length > 0 && (
            <ul className="mt-12 divide-y border-y font-mono text-xs uppercase tracking-wider text-muted-foreground">
              {facts.map((fact) => (
                <li key={fact} className="py-2.5">
                  {fact}
                </li>
              ))}
            </ul>
          )}
        </div>

        <figure className="md:col-span-7">
          {/* The photo uncovers from the bottom once, like a print sliding out of a sleeve. */}
          <motion.div
            className="relative aspect-[4/5] overflow-hidden bg-muted sm:aspect-[5/4] md:aspect-[4/5]"
            initial={reduceMotion ? false : { clipPath: "inset(100% 0 0 0)" }}
            animate={{ clipPath: "inset(0% 0 0 0)" }}
            transition={{ duration: 1.1, ease: EASE }}
          >
            <motion.img
              src={image.src}
              alt={image.alt}
              width={1200}
              height={1500}
              className="absolute inset-0 size-full object-cover"
              initial={reduceMotion ? false : { scale: 1.12 }}
              animate={{ scale: 1 }}
              transition={{ duration: 1.6, ease: EASE }}
            />
          </motion.div>
          {(image.caption || image.credit) && (
            <figcaption className="mt-3 flex flex-wrap justify-between gap-x-6 gap-y-1 font-mono text-xs text-muted-foreground">
              {image.caption && <span>{image.caption}</span>}
              {image.credit && <span>{image.credit}</span>}
            </figcaption>
          )}
        </figure>
      </div>
    </section>
  );
}
```
