Editorial Hero
A magazine opening: a large serif headline, the intro and actions, tags, and the photo framed tall or set as a band.
npx shadcn@latest add @vibebox/hero-editorialControls
<HeroEditorial />About
Rebuilds the site kit's editorial intro and text-only heroes. The serif headline gives a crafted, magazine feel for studios, food and hospitality. It differs from hero-kinetic-type (animated sans poster type) and hero-centered.
Use it when
- The brand voice matters as much as the photo.
- There is one strong portrait photo, or none yet.
Skip it when
- The page needs a booking stage: use
hero-split-stage. - The headline is a product claim for software: use
hero-centered.
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-editorial - 3Use it:
import { HeroEditorial } from "@/components/vibebox/hero-editorial"; import { home } from "@/content/pages/home"; <HeroEditorial content={home.hero} variant="framed" id="top" />
Code
2 filescomponents/vibebox/hero-editorial/index.tsx
/**
* VibeBox Motion · Editorial 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 type { Image } from "@/lib/vibebox/content";
import { cn } from "@/lib/utils";
import type { Content, Variant } from "./schema";
/*
* A magazine opening: a serif headline set large, the intro and actions, a row of tags, and the
* photo either framed tall beside the copy or as a slim band under the type.
*/
const FALLBACK_MEDIA: Image = { src: "scene:window", alt: "", width: 1200, height: 1500 };
export type HeroEditorialProps = BlockProps<Content, Variant>;
export function HeroEditorial({ content, variant = "framed", id, tone, className }: HeroEditorialProps) {
const framed = variant === "framed";
return (
<Section
slug="hero-editorial"
variant={variant}
id={id}
tone={tone}
labelledBy={headingId(id)}
spacing={false}
className={cn("overflow-clip py-[clamp(48px,8vw,120px)]", className)}
>
<Container className={cn("grid grid-cols-[minmax(0,1fr)] gap-[clamp(36px,5vw,64px)]", framed && "lg:grid-cols-[minmax(0,1.3fr)_minmax(0,0.7fr)] lg:items-end")}>
<div className="grid gap-6">
<Eyebrow value={content.eyebrow} />
<SlotText
as="h1"
value={content.heading}
path="heading"
id={headingId(id)}
className={cn("font-serif font-normal tracking-[-0.035em] text-balance", framed ? "text-h1" : "text-hero")}
/>
<div className={cn("grid gap-6", !framed && "lg:grid-cols-[minmax(0,1fr)_auto] lg:items-end lg:gap-12")}>
<SlotText as="p" value={content.lead} path="lead" className="max-w-[54ch] text-[clamp(1.05rem,1.3vw,1.25rem)] leading-relaxed text-pretty text-muted-foreground" />
<Actions primary={content.primaryAction} secondary={content.secondaryAction} size="lg" />
</div>
{content.tags.length > 0 && (
<ul className="flex flex-wrap gap-x-5 gap-y-2 border-t border-border pt-5 text-small font-medium text-muted-foreground">
{content.tags.map((tag, index) => (
<li key={index} className="flex items-center gap-2">
<span aria-hidden="true" className="size-1 rounded-full bg-spectrum-3" />
<span data-slot={`tags.${index}`}>{tag}</span>
</li>
))}
</ul>
)}
</div>
<figure className={cn("m-0 grid gap-3", REVEAL)}>
<div className={cn("overflow-hidden rounded-xl ring-1 ring-border", framed ? "aspect-[4/5]" : "aspect-[16/7] sm:aspect-[21/7]")}>
<Media image={content.media ?? FALLBACK_MEDIA} priority sizes={framed ? "(min-width: 1024px) 35vw, 100vw" : "100vw"} />
</div>
<SlotText as="figcaption" value={content.caption} path="caption" className="text-small text-muted-foreground" />
</figure>
</Container>
</Section>
);
}components/vibebox/hero-editorial/schema.ts
/**
* VibeBox Motion · Editorial 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 = ["framed", "type-led"] as const;
export type Variant = (typeof variants)[number];
export const content = sections.hero.extend({
caption: text({ role: "Photo caption", max: 60, fillIn: true }).optional(),
tags: list(text({ role: "Tag", max: 24 }), { role: "Tags", min: 0, max: 6 }),
});
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 | — | — |
| caption | Photo caption | text | 60 chars | Can be a fill-in |
| tags | Tags | list | up to 6 | — |
| tags[] | Tag | text | 24 chars | — |
Variants
Frameddefault
framed
Copy on the left, a tall 4:5 framed photo and caption on the right.
Type-led
type-led
The headline at hero size across the page, the photo as a slim band beneath.
Accessibility and motion
- Keyboard
- The actions, in reading order.
- Screen readers
- One h1, the intro, the actions, a list of tags, and a figure with its caption.
- Touch
- Nothing depends on hover.
- Reduced motion
- The photo appears without its reveal.
Edge cases and limits
- No photo: a placeholder scene.
- No caption: the figure has none.
Related

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

A headline that names each audience in turn, the last word melting into the next.
Heroes block · Server

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