Centered Hero
One centred promise with its actions and highlights, the image below, on a plain, haloed or gradient panel ground.
npx shadcn@latest add @vibebox/hero-centeredControls
<HeroCentered />About
Rebuilds the site kit's centred stack, radial centred and gradient accent heroes as three variants of one block. Use it where the message matters more than a scene. It differs from hero-split-stage (copy beside a stage) and hero-editorial (serif type, framed photo).
Use it when
- One message and one action carry the page.
- The image is wide (a product shot, a room, a team).
Skip it when
- The image is tall or a portrait: use
hero-editorial. - The business sells bookings and wants to show one: use
hero-split-stage.
Install
- 1Add the VibeBox registry to
components.json:{ "registries": { "@vibebox": "https://motion.vibeboxph.com/r/{name}.json" } } - 2Add the block with the shadcn CLI:
npx shadcn@latest add @vibebox/hero-centered - 3Use it:
import { HeroCentered } from "@/components/vibebox/hero-centered"; import { home } from "@/content/pages/home"; <HeroCentered content={home.hero} variant="stack" id="top" />
Code
2 filescomponents/vibebox/hero-centered/index.tsx
/**
* VibeBox Motion · Centered Hero
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
import { Actions, Container, Eyebrow, headingId, Media, REVEAL, Section, SlotText, type BlockProps } from "@/lib/vibebox/block-kit";
import { cn } from "@/lib/utils";
import type { Content, Variant } from "./schema";
/*
* One promise in the middle of the page: the headline, intro and actions centred, a row of
* highlights, and the media below. `halo` rings the copy with soft circles; `panel` sets it all
* on a gradient surface.
*/
export type HeroCenteredProps = BlockProps<Content, Variant>;
export function HeroCentered({ content, variant = "stack", id, tone, className }: HeroCenteredProps) {
const halo = variant === "halo";
const panel = variant === "panel";
const copy = (
<div className="relative mx-auto grid max-w-4xl justify-items-center gap-6 text-center">
<Eyebrow value={content.eyebrow} />
<SlotText as="h1" value={content.heading} path="heading" id={headingId(id)} reveal className="font-display text-h1 font-semibold tracking-[-0.04em] text-balance" />
<SlotText as="p" value={content.lead} path="lead" className="max-w-[56ch] text-[clamp(1.05rem,1.3vw,1.25rem)] leading-relaxed text-pretty text-muted-foreground" />
<Actions primary={content.primaryAction} secondary={content.secondaryAction} size="lg" className="mt-1 justify-center" />
{content.highlights.length > 0 && (
<ul className="mt-2 flex flex-wrap justify-center gap-2">
{content.highlights.map((item, index) => (
<li key={index} data-slot={`highlights.${index}`} className="rounded-full border border-border bg-card/70 px-3.5 py-1.5 text-small font-medium">
{item}
</li>
))}
</ul>
)}
</div>
);
const media = content.media && (
<div
className={cn(
"relative mx-auto overflow-hidden rounded-xl ring-1 ring-border",
halo ? "aspect-square w-[min(300px,72vw)]" : "aspect-[16/9] w-full max-w-5xl",
REVEAL,
)}
>
<Media image={content.media} priority sizes="(min-width: 1024px) 1024px, 100vw" />
</div>
);
return (
<Section
slug="hero-centered"
variant={variant}
id={id}
tone={tone}
labelledBy={headingId(id)}
spacing={false}
className={cn("overflow-clip py-[clamp(48px,8vw,120px)]", className)}
>
{halo && (
<div
aria-hidden="true"
className="pointer-events-none absolute top-[38%] left-1/2 -z-10 aspect-square w-[min(980px,140vw)] -translate-x-1/2 -translate-y-1/2 rounded-full [mask-image:radial-gradient(circle,black_28%,transparent_70%)]"
style={{ background: "repeating-radial-gradient(circle, transparent 0 46px, color-mix(in oklab, var(--spectrum-3) 30%, transparent) 47px 48px)" }}
/>
)}
<Container>
<div
className={cn(
"grid gap-[clamp(40px,6vw,72px)]",
panel && "rounded-xl px-5 py-12 ring-1 ring-border sm:px-10 sm:py-16 lg:px-16",
)}
style={
panel
? { background: "linear-gradient(150deg, color-mix(in oklab, var(--spectrum-2) 14%, var(--card)), color-mix(in oklab, var(--spectrum-5) 10%, var(--card)))" }
: undefined
}
>
{copy}
{media}
</div>
</Container>
</Section>
);
}components/vibebox/hero-centered/schema.ts
/**
* VibeBox Motion · Centered Hero
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
import { list, sections, text, z } from "@/lib/vibebox/content";
export const variants = ["stack", "halo", "panel"] as const;
export type Variant = (typeof variants)[number];
export const content = sections.hero.extend({
highlights: list(text({ role: "Highlight", max: 28 }), { role: "Highlights", min: 0, max: 5 }),
});
export type Content = z.infer<typeof content>;Content
Typed slots from schema.ts. Facts (hours, prices, quotes, names) come only from the business; until then they render as fill-ins.
| Slot | Label | Type | Limit | Fact |
|---|---|---|---|---|
| eyebrow | Kicker | text | 32 chars | — |
| heading | Headline | text | 80 chars | — |
| lead | Intro | text | 160 chars | — |
| primaryAction | Main button | action | 24 chars | — |
| secondaryAction | Second button | action | 24 chars | — |
| media | Image | image | — | — |
| highlights | Highlights | list | up to 5 | — |
| highlights[] | Highlight | text | 28 chars | — |
Variants
Stackdefault
stack
Centred copy with a wide 16:9 image below.
Halo
halo
Centred copy inside soft rings, a small square image below.
Gradient panel
panel
Copy and image on a rounded gradient surface.
Accessibility and motion
- Keyboard
- The actions, in reading order.
- Screen readers
- One h1, the intro, the actions and a list of highlights; the halo is hidden.
- Touch
- Nothing depends on hover.
- Reduced motion
- The image appears without its reveal.
Edge cases and limits
- No image: the copy stands alone.
- Five long highlights wrap onto two lines.
Related

Bento Hero
FreeThe promise beside a three-tile mosaic: a tall photo, a second image and a card of what the business offers.
Heroes block · Server

Editorial Hero
FreeA magazine opening: a large serif headline, the intro and actions, tags, and the photo framed tall or set as a band.
Heroes block · Server

Poster-size type that rises word by word on load, with the photo in a corner or set wide beneath.
Heroes block · Server