Hero
Hero Video
A centered label pill, heading, lead and two buttons above a wide video that loads and plays on click.
Layout and motion from the Findit template — see it in contextimport { FlipButton } from "@/components/ui/flip-button";
import { HeroVideo as Video } from "@/components/ui/hero-video";
import { cn } from "@/lib/utils";
/*
* Hero Video — a centered label pill, the heading, a lead and two buttons above a wide video that plays on click.
* Source: marcframe/findit → components/sections/HomeHeroSection.tsx (+ design-system HeroVideo, Button shadow /
* secondary).
*
* Layout kept from Framer: the text column is centered (up to 350px, 500px from 768px, 750px from 1024px) with the
* video below it, 64px apart (80px from 768px, 96px from 1024px); the video is 16:9, 550px tall from 1024px. The
* section fades from the muted surface to the background over its last quarter.
* Motion kept (hero-video): nothing of the video loads until it is played; the round play button sits at 75% and comes
* to full on hover (spring bounce 0, 0.4s). The buttons roll their label (flip-button).
* Changed: Findit's star in a black dot and white pill become the primary color and the card surface.
*/
export type HeroVideoProps = {
/** Heading level: h1 when the hero opens the page. */
as?: "h1" | "h2";
/** Short line in the pill above the heading. */
label?: string;
title?: string;
lead?: string;
/** Filled and secondary buttons. */
actions?: { label: string; href: string }[];
video?: {
src: string;
/** Shown until the video plays. */
poster: string;
/** What the play button does, e.g. "Play the tour of our homes". */
label: string;
};
className?: string;
};
const defaults = {
label: "Your trusted partner in real estate",
title: "Your next home is one visit away",
lead: "Handpicked homes that fit the way you live — whether you're buying, renting or selling.",
actions: [
{ label: "Explore properties", href: "/properties" },
{ label: "Book a visit", href: "/contact" },
],
video: {
/* Pexels (free to use): "Aerial view of a house with a pool" — replace with your own clip. */
src: "https://videos.pexels.com/video-files/10959786/10959786-sd_960_540_30fps.mp4",
poster: "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?auto=format&fit=crop&w=1800&q=80",
label: "Play the property tour video",
},
};
export function HeroVideo({
as: Heading = "h1",
label = defaults.label,
title = defaults.title,
lead = defaults.lead,
actions = defaults.actions,
video = defaults.video,
className,
}: HeroVideoProps) {
const [primary, secondary] = actions;
return (
<section className={cn("bg-linear-to-b from-muted/50 from-75% to-background py-(--section-y)", className)}>
<div className="mx-auto flex max-w-(--container) flex-col items-center gap-16 px-(--gutter) md:gap-20 lg:gap-24">
<div className="flex max-w-88 flex-col items-center gap-7.5 text-center md:max-w-125 lg:max-w-187.5">
<div className="flex flex-col items-center gap-5">
<div className="flex flex-col items-center gap-2.5">
{label && (
<p className="flex items-center gap-2.5 rounded-full border bg-card py-1 pr-4 pl-1 text-sm text-card-foreground shadow-xs">
<span aria-hidden className="grid size-6 place-items-center rounded-full bg-primary text-primary-foreground shadow-sm">
<svg viewBox="0 0 16 16" fill="currentColor" className="size-3.5">
<path d="M8 1.5l1.9 4.1 4.5.5-3.4 3 1 4.4L8 11.2l-4 2.3 1-4.4-3.4-3 4.5-.5z" />
</svg>
</span>
{label}
</p>
)}
<Heading className="font-heading text-display font-semibold tracking-tight text-balance">{title}</Heading>
</div>
{lead && <p className="max-w-125 text-lead text-pretty text-muted-foreground">{lead}</p>}
</div>
{actions.length > 0 && (
<div className="flex flex-wrap items-center justify-center gap-2.5">
{primary && (
<FlipButton href={primary.href} size="lg">
{primary.label}
</FlipButton>
)}
{secondary && (
<FlipButton href={secondary.href} size="lg" variant="outline">
{secondary.label}
</FlipButton>
)}
</div>
)}
</div>
{video && (
<Video src={video.src} poster={video.poster} label={video.label} className="w-full rounded-xl lg:aspect-auto lg:h-137.5" />
)}
</div>
</section>
);
}Installation
$ npx shadcn@latest add https://framebrick.co/r/blocks/hero-video.json1. Add the Framebrick UI components it uses (they bring Motion).
npx shadcn@latest add https://framebrick.co/r/flip-button.json https://framebrick.co/r/hero-video.json2. Add the block tokens to
app/globals.cssonce (theming).npx shadcn@latest add https://framebrick.co/r/blocks/tokens.json3. Copy the block into
components/hero-video.tsx.import { FlipButton } from "@/components/ui/flip-button"; import { HeroVideo as Video } from "@/components/ui/hero-video"; import { cn } from "@/lib/utils"; /* * Hero Video — a centered label pill, the heading, a lead and two buttons above a wide video that plays on click. * Source: marcframe/findit → components/sections/HomeHeroSection.tsx (+ design-system HeroVideo, Button shadow / * secondary). * * Layout kept from Framer: the text column is centered (up to 350px, 500px from 768px, 750px from 1024px) with the * video below it, 64px apart (80px from 768px, 96px from 1024px); the video is 16:9, 550px tall from 1024px. The * section fades from the muted surface to the background over its last quarter. * Motion kept (hero-video): nothing of the video loads until it is played; the round play button sits at 75% and comes * to full on hover (spring bounce 0, 0.4s). The buttons roll their label (flip-button). * Changed: Findit's star in a black dot and white pill become the primary color and the card surface. */ export type HeroVideoProps = { /** Heading level: h1 when the hero opens the page. */ as?: "h1" | "h2"; /** Short line in the pill above the heading. */ label?: string; title?: string; lead?: string; /** Filled and secondary buttons. */ actions?: { label: string; href: string }[]; video?: { src: string; /** Shown until the video plays. */ poster: string; /** What the play button does, e.g. "Play the tour of our homes". */ label: string; }; className?: string; }; const defaults = { label: "Your trusted partner in real estate", title: "Your next home is one visit away", lead: "Handpicked homes that fit the way you live — whether you're buying, renting or selling.", actions: [ { label: "Explore properties", href: "/properties" }, { label: "Book a visit", href: "/contact" }, ], video: { /* Pexels (free to use): "Aerial view of a house with a pool" — replace with your own clip. */ src: "https://videos.pexels.com/video-files/10959786/10959786-sd_960_540_30fps.mp4", poster: "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?auto=format&fit=crop&w=1800&q=80", label: "Play the property tour video", }, }; export function HeroVideo({ as: Heading = "h1", label = defaults.label, title = defaults.title, lead = defaults.lead, actions = defaults.actions, video = defaults.video, className, }: HeroVideoProps) { const [primary, secondary] = actions; return ( <section className={cn("bg-linear-to-b from-muted/50 from-75% to-background py-(--section-y)", className)}> <div className="mx-auto flex max-w-(--container) flex-col items-center gap-16 px-(--gutter) md:gap-20 lg:gap-24"> <div className="flex max-w-88 flex-col items-center gap-7.5 text-center md:max-w-125 lg:max-w-187.5"> <div className="flex flex-col items-center gap-5"> <div className="flex flex-col items-center gap-2.5"> {label && ( <p className="flex items-center gap-2.5 rounded-full border bg-card py-1 pr-4 pl-1 text-sm text-card-foreground shadow-xs"> <span aria-hidden className="grid size-6 place-items-center rounded-full bg-primary text-primary-foreground shadow-sm"> <svg viewBox="0 0 16 16" fill="currentColor" className="size-3.5"> <path d="M8 1.5l1.9 4.1 4.5.5-3.4 3 1 4.4L8 11.2l-4 2.3 1-4.4-3.4-3 4.5-.5z" /> </svg> </span> {label} </p> )} <Heading className="font-heading text-display font-semibold tracking-tight text-balance">{title}</Heading> </div> {lead && <p className="max-w-125 text-lead text-pretty text-muted-foreground">{lead}</p>} </div> {actions.length > 0 && ( <div className="flex flex-wrap items-center justify-center gap-2.5"> {primary && ( <FlipButton href={primary.href} size="lg"> {primary.label} </FlipButton> )} {secondary && ( <FlipButton href={secondary.href} size="lg" variant="outline"> {secondary.label} </FlipButton> )} </div> )} </div> {video && ( <Video src={video.src} poster={video.poster} label={video.label} className="w-full rounded-xl lg:aspect-auto lg:h-137.5" /> )} </div> </section> ); }
Usage
import { HeroVideo } from "@/components/hero-video";
<HeroVideo
label="Your trusted partner in real estate"
title="Your next home is one visit away"
actions={[
{ label: "Explore properties", href: "/properties" },
{ label: "Book a visit", href: "/contact" },
]}
video={{ src: "/video/tour.mp4", poster: "/video/tour.jpg", label: "Play the property tour video" }}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| as | "h1" | "h2" | "h1" | Heading level — h1 when the hero opens the page. |
| label | string | — | Short line in the pill above the heading, beside a star in the primary color. |
| title / lead | string | — | Heading (text-display) and a muted lead under it. |
| actions | { label, href }[] | — | Up to two buttons: filled, then outlined. |
| video | { src, poster, label } | — | An MP4 (or WebM) file, the still shown until it plays, and what the play button says. |
| className | string | — | Classes for the <section>. |
- Nothing of the video loads until it is played (preload none); the whole frame is the play button. The round button sits at 75% and comes to full on hover (hero-video, spring bounce 0, 0.4s); it comes back when the video is paused or ends.
- 16:9 below 1024px, 550px tall from 1024px. The section fades from the muted surface to the background over its last quarter.
- Installs as components/hero-video.tsx, beside the player it uses (components/ui/hero-video.tsx, imported as Video).
- The demo clip is a free Pexels video streamed from Pexels; host your own file for production.
AI prompt
Paste into v0, Lovable, Bolt or Claude to rebuild this block in your own project.
Create a React + Tailwind CSS block called HeroVideo (Next.js). - <section> with a vertical gradient from muted/50 (to 75%) to the background, py-(--section-y); container mx-auto max-w-(--container) px-(--gutter), a centered column (gap-16, md:gap-20, lg:gap-24). - Text column, centered, max 350px / 500px (768px) / 750px (1024px), gap-7.5: a pill (rounded-full, border, bg-card, shadow-xs, text-sm, py-1 pl-1 pr-4) with a 24px primary circle holding a star; the h1 (font-heading text-display semibold, tracking-tight, balanced); a muted lead (text-lead, max 500px); two buttons (lg): filled and outline, whose label rolls up while a copy rises from below (spring bounce 0, 0.4s). - Below: a 16:9 video frame (rounded-xl, lg: 550px tall) that is one <button>: a <video preload="none" muted loop playsInline> with a poster, and a 48px round play button (bg-black/25, backdrop blur, white triangle) at 75% opacity, 100% on hover and focus (spring bounce 0, 0.4s); click toggles play/pause; the play button shows while paused. - Only shadcn variables and block tokens.











