A conference hero with an aftermovie and a bolder headline. The date and city chips live inside the halves, so they fly off with the words.
@keyframeui
registry/demos/expanding-hero-event-demo.tsx
"use client";
import * as React from "react";
import { ExpandingHero } from "@/components/ui/expanding-hero";
const film = "https://assets.mixkit.co/videos/13222/13222-720.mp4";
const poster = "https://assets.mixkit.co/videos/13222/13222-thumb-720-0.jpg";
const days = [
{
day: "Day 1",
date: "Thu, Sept 24",
sessions: ["Opening keynote", "The craft of small details", "Designing for motion", "Evening party"],
},
{
day: "Day 2",
date: "Fri, Sept 25",
sessions: ["Tools for thinking", "Building design systems that last", "Live redesign", "Closing keynote"],
},
];
export function ExpandingHeroEventDemo() {
const scrollRef = React.useRef<HTMLDivElement>(null);
const filmRef = React.useRef<HTMLVideoElement>(null);
return (
<div className="relative w-full overflow-hidden rounded-xl border border-zinc-200 bg-white text-zinc-950 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50">
<div ref={scrollRef} className="relative h-[var(--demo-height,36rem)] overflow-y-auto overscroll-contain">
<ExpandingHero
containerRef={scrollRef}
stageClassName="h-[var(--demo-height,36rem)]"
headlineClassName="font-bold tracking-[-0.06em] text-[clamp(3rem,19cqw,7rem)] @3xl:text-[clamp(3rem,11cqw,11rem)]"
windowClassName="@3xl:w-[1.8em]"
radius="0.3em"
// Anything inside a half travels with it, so the chips fly off with the words.
before={
<span className="relative">
Config
<Chip className="bottom-full left-[0.08em] mb-3">
<span className="size-1.5 rounded-full bg-orange-500" />
Sept 24–25
</Chip>
</span>
}
after={
<span className="relative">
2026
<Chip className="top-full right-[0.08em] mt-3">San Francisco</Chip>
</span>
}
media={<video ref={filmRef} src={film} poster={poster} autoPlay muted loop playsInline />}
overlay={
<button
type="button"
onClick={() => watch(filmRef.current)}
className="flex items-center gap-3 rounded-full bg-white py-2 pr-5 pl-2 text-sm font-medium text-zinc-950 shadow-xl shadow-black/30 transition-transform duration-150 ease-out active:scale-[0.97]"
>
<span className="grid size-8 place-items-center rounded-full bg-orange-500 text-white">
<svg viewBox="0 0 16 16" aria-hidden className="ml-0.5 size-3 fill-current">
<path d="M4 2.6v10.8a.6.6 0 0 0 .9.5l8.6-5.4a.6.6 0 0 0 0-1L4.9 2.1a.6.6 0 0 0-.9.5Z" />
</svg>
</span>
Watch the 2025 aftermovie
</button>
}
>
<p className="mt-12 max-w-xs text-base text-balance text-zinc-600 @3xl:mt-6 sm:text-lg dark:text-zinc-400">
Two days on design, craft and the tools behind them.
</p>
<a
href="#tickets"
className="rounded-full bg-orange-500 px-5 py-2.5 text-sm font-medium text-white transition-[background-color,scale] duration-150 ease-out hover:bg-orange-600 active:scale-[0.97]"
>
Get tickets
</a>
</ExpandingHero>
<section className="mx-auto max-w-3xl px-6 py-16 sm:py-24">
<h2 className="text-sm font-medium text-zinc-500">Schedule</h2>
<div className="mt-6 grid gap-10 sm:grid-cols-2">
{days.map((day) => (
<div key={day.day}>
<p className="flex items-baseline justify-between border-b border-zinc-950/10 pb-3 dark:border-white/10">
<span className="text-lg font-semibold tracking-tight">{day.day}</span>
<span className="text-sm text-zinc-500">{day.date}</span>
</p>
<ul className="mt-3 space-y-2.5 text-sm text-zinc-600 dark:text-zinc-400">
{day.sessions.map((session) => (
<li key={session}>{session}</li>
))}
</ul>
</div>
))}
</div>
</section>
</div>
</div>
);
}
function Chip({ className, children }: { className?: string; children: React.ReactNode }) {
return (
<span
className={`absolute flex items-center gap-2 rounded-full px-3 py-1.5 text-[13px] leading-none font-medium tracking-normal whitespace-nowrap text-zinc-600 ring-1 ring-zinc-950/10 dark:text-zinc-300 dark:ring-white/15 ${className}`}
>
{children}
</span>
);
}
/** Plays the film from the top, full screen. */
function watch(video: HTMLVideoElement | null) {
if (!video) return;
video.currentTime = 0;
void video.play();
if (video.requestFullscreen) void video.requestFullscreen();
else (video as HTMLVideoElement & { webkitEnterFullscreen?: () => void }).webkitEnterFullscreen?.();
}
Live preview, examples and props: https://keyframeui.com/components/expanding-hero#expanding-hero-event-demo




























