Skip to content
Motion
FreeIn reviewv1.0.0

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-centered

Controls

Variant
<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

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the block with the shadcn CLI:
    npx shadcn@latest add @vibebox/hero-centered
  3. 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 files
components/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.

SlotLabelTypeLimitFact
eyebrowKickertext32 chars—
headingHeadlinetext80 chars—
leadIntrotext160 chars—
primaryActionMain buttonaction24 chars—
secondaryActionSecond buttonaction24 chars—
mediaImageimage——
highlightsHighlightslistup to 5—
highlights[]Highlighttext28 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.
  • A headline about moving better beside a mosaic of a tile scene, a bloom scene and a class list

    The promise beside a three-tile mosaic: a tall photo, a second image and a card of what the business offers.

    Heroes block · Server

  • A large serif headline about bread beside a framed window scene with a caption fill-in

    A magazine opening: a large serif headline, the intro and actions, tags, and the photo framed tall or set as a band.

    Heroes block · Server

  • Giant headline type about memorable brands with a ribbon scene in the corner

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

    Heroes block · Server

↑ ↓ to move↵ to openesc to close