Hero Video Dialog
Hero Video Dialog is a React and Tailwind CSS overlay component from Magic UI, licensed MIT. Copy it and paste it into your project.
Magic UI
MIT
overlays
What it is
Hero Video Dialog is a React and Tailwind CSS overlay component from Magic UI, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
Needs
npm i motion
Licence
This component comes from Magic UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Hero Video Dialog
"use client"
import { useState } from "react"
import { Play, XIcon } from "lucide-react"
import { AnimatePresence, motion } from "motion/react"
import { cn } from "@/lib/utils"
type AnimationStyle =
| "from-bottom"
| "from-center"
| "from-top"
| "from-left"
| "from-right"
| "fade"
| "top-in-bottom-out"
| "left-in-right-out"
interface HeroVideoProps {
animationStyle?: AnimationStyle
videoSrc: string
thumbnailSrc: string
thumbnailAlt?: string
className?: string
}
const animationVariants = {
"from-bottom": {
initial: { y: "100%", opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: "100%", opacity: 0 },
},
"from-center": {
initial: { scale: 0.5, opacity: 0 },
animate: { scale: 1, opacity: 1 },
exit: { scale: 0.5, opacity: 0 },
},
"from-top": {
initial: { y: "-100%", opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: "-100%", opacity: 0 },
},
"from-left": {
initial: { x: "-100%", opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: "-100%", opacity: 0 },
},
"from-right": {
initial: { x: "100%", opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: "100%", opacity: 0 },
},
fade: {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
},
"top-in-bottom-out": {
initial: { y: "-100%", opacity: 0 },
animate: { y: 0, opacity: 1 },
exit: { y: "100%", opacity: 0 },
},
"left-in-right-out": {
initial: { x: "-100%", opacity: 0 },
animate: { x: 0, opacity: 1 },
exit: { x: "100%", opacity: 0 },
},
}
export function HeroVideoDialog({
animationStyle = "from-center",
videoSrc,
thumbnailSrc,
thumbnailAlt = "Video thumbnail",
className,
}: HeroVideoProps) {
const [isVideoOpen, setIsVideoOpen] = useState(false)
const selectedAnimation = animationVariants[animationStyle]
return (
<div className={cn("relative", className)}>
<button
type="button"
aria-label="Play video"
className="group relative cursor-pointer border-0 bg-transparent p-0"
onClick={() => setIsVideoOpen(true)}
>
<img
src={thumbnailSrc}
alt={thumbnailAlt}
width={1920}
height={1080}
className="w-full rounded-md border shadow-lg transition-all duration-200 ease-out group-hover:brightness-[0.8]"
/>
<div className="absolute inset-0 flex scale-[0.9] items-center justify-center rounded-2xl transition-all duration-200 ease-out group-hover:scale-100">
<div className="bg-primary/10 flex size-28 items-center justify-center rounded-full backdrop-blur-md">
<div
className={`from-primary/30 to-primary relative flex size-20 scale-100 items-center justify-center rounded-full bg-linear-to-b shadow-md transition-all duration-200 ease-out group-hover:scale-[1.2]`}
>
<Play
className="size-8 scale-100 fill-white text-white transition-transform duration-200 ease-out group-hover:scale-105"
style={{
filter:
"drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06))",
}}
/>
</div>
</div>
</div>
</button>
<AnimatePresence>
{isVideoOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Escape" || e.key === "Enter" || e.key === " ") {
setIsVideoOpen(false)
}
}}
onClick={() => setIsVideoOpen(false)}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-md"
>
<motion.div
{...selectedAnimation}
transition={{ type: "spring", damping: 30, stiffness: 300 }}
className="relative mx-4 aspect-video w-full max-w-4xl md:mx-0"
>
<motion.button className="absolute -top-16 right-0 rounded-full bg-neutral-900/50 p-2 text-xl text-white ring-1 backdrop-blur-md dark:bg-neutral-100/50 dark:text-black">
<XIcon className="size-5" />
</motion.button>
<div className="relative isolate z-1 size-full overflow-hidden rounded-2xl border-2 border-white">
<iframe
src={videoSrc}
title="Hero Video player"
className="mt-0 size-full rounded-2xl"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
></iframe>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}