Kinetic Type Hero
Poster-size type that rises word by word on load, with the photo in a corner or set wide beneath.
npx shadcn@latest add @vibebox/hero-kinetic-typeControls
<HeroKineticType />About
Rebuilds the site kit's fullscreen type hero on the library's Kinetic Headline: the words rise on load in CSS alone. For brands that lead with words: studios, venues, launches. It differs from hero-editorial (static serif) and the Pro hero-word-swap (rotating words).
Use it when
- The headline is short and confident.
- The brand wants energy without 3D.
Skip it when
- The headline is long and explanatory: use
hero-centered. - Motion is set to none for the brand: any static hero reads better.
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-kinetic-type - 3Use it:
import { HeroKineticType } from "@/components/vibebox/hero-kinetic-type"; import { home } from "@/content/pages/home"; <HeroKineticType content={home.hero} variant="poster" id="top" />
Code
2 filescomponents/vibebox/hero-kinetic-type/index.tsx
/**
* VibeBox Motion · Kinetic Type 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 { KineticHeadline, type KineticSegment } from "@/components/vibebox/kinetic-headline";
import type { Image } from "@/lib/vibebox/content";
import { cn } from "@/lib/utils";
import type { Content, Variant } from "./schema";
/*
* Poster type: the headline set as large as the screen allows, each word rising into place on
* load (Kinetic Headline, CSS only), with the photo tucked into a corner or set wide beneath.
*/
const FALLBACK_MEDIA: Image = { src: "scene:ribbon", alt: "", width: 1200, height: 900 };
/** `*word*` becomes the serif emphasis segment. */
function segments(heading: string): KineticSegment[] {
return heading.split("*").flatMap((part, index): KineticSegment[] => (part ? [index % 2 === 1 ? { text: part, variant: "serif" } : part] : []));
}
export type HeroKineticTypeProps = BlockProps<Content, Variant>;
export function HeroKineticType({ content, variant = "poster", id, tone, className }: HeroKineticTypeProps) {
const poster = variant === "poster";
return (
<Section
slug="hero-kinetic-type"
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(32px,5vw,64px)]", poster && "lg:grid-cols-[minmax(0,1.65fr)_minmax(0,0.85fr)] lg:items-end")}>
<div className="grid gap-7">
<Eyebrow value={content.eyebrow} />
<div data-slot="heading">
<KineticHeadline
as="h1"
id={headingId(id)}
segments={segments(content.heading)}
className="font-display text-[clamp(2.9rem,1rem+7.6vw,8.5rem)] leading-[0.94] font-semibold tracking-[-0.05em]"
/>
</div>
<div className={cn("grid gap-6", !poster && "md:grid-cols-[minmax(0,1fr)_auto] md:items-end md:gap-12")}>
<SlotText as="p" value={content.lead} path="lead" className="max-w-[52ch] 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>
</div>
<div className={cn("overflow-hidden rounded-xl ring-1 ring-border", poster ? "aspect-[4/3] lg:aspect-[3/4]" : "aspect-[16/7] sm:aspect-[21/8]", REVEAL)}>
<Media image={content.media ?? FALLBACK_MEDIA} priority sizes={poster ? "(min-width: 1024px) 30vw, 100vw" : "100vw"} />
</div>
</Container>
</Section>
);
}components/vibebox/hero-kinetic-type/schema.ts
/**
* VibeBox Motion · Kinetic Type 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 { sections, type z } from "@/lib/vibebox/content";
export const variants = ["poster", "stacked"] as const;
export type Variant = (typeof variants)[number];
export const content = sections.hero;
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 | — | — |
Variants
Posterdefault
poster
Type on the left, a 3:4 photo bottom right on desktop.
Stacked
stacked
Type across the page, intro beside the actions, a wide photo band beneath.
Accessibility and motion
- Keyboard
- The actions, in reading order.
- Screen readers
- The h1 reads as one sentence; the word masks are layout only.
- Touch
- Nothing depends on hover.
- Reduced motion
- The words are in place at once; nothing rises.
Edge cases and limits
- An 80-character headline wraps to four lines on phones.
- No photo: a placeholder scene.
Related

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

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