Please use Desktop to view and interact with components
Bento Grid Block
Layered bento grid with imagery, metrics, and smooth motion reveals
Preview
Bento grid storytelling with layered motion
Blend narrative, metrics, and rich visuals into a single responsive layout powered by smooth Framer Motion transitions.
Designing delightful product experiences.
We craft motion systems that celebrate micro-interactions and elevate usability across complex product surfaces.
Project satisfaction
98%
+4.2% vs last quarter
Delivery cadence
2.4x
Faster than baseline
Retention rate
92%
After 6 months
Immersive motion prototypes with cinematic lighting
Layered light, shadow, and depth cues help teams experience the product as it will ship—before the first line of code.
From first sketch to polished prototype in seven days
We compress discovery, exploration, and refinement into a focused week-long sprint so your team can feel the flow of the final experience sooner.
Watch our latest animation breakdown
A 3-minute deep dive into how we choreograph timing curves, orchestrate transitions, and build fluid component interactions.
Capturing texture, light, and pace for new explorations
A snapshot of the references that steer our motion language and narrative rhythm.
This component requires shadcn/ui
This component uses shadcn/ui components. Make sure you have shadcn/ui set up in your project.
- Install shadcn/ui:
npx shadcn-ui@latest init - Install required components based on the imports in the code (e.g.,
npx shadcn-ui@latest add button) - Ensure your
tailwind.config.tsandglobals.cssare configured as per shadcn/ui documentation
Code
'use client'
import { motion } from 'framer-motion'
import { ArrowUpRight, PlayCircle, Sparkles } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
const avatarImages = [
{
src: 'https://images.unsplash.com/photo-1544723795-3fb6469f5b39?w=200&h=200&fit=crop&q=80',
alt: 'Motion designer portrait',
},
{
src: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=200&h=200&fit=crop&q=80',
alt: 'Product strategist portrait',
},
{
src: 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=200&h=200&fit=crop&q=80',
alt: 'UX researcher portrait',
},
]
const metrics = [
{
label: 'Project satisfaction',
value: '98%',
caption: '+4.2% vs last quarter',
},
{
label: 'Delivery cadence',
value: '2.4x',
caption: 'Faster than baseline',
},
{
label: 'Retention rate',
value: '92%',
caption: 'After 6 months',
},
]
const processSteps = [
{
label: 'Ideate & storyboard',
progress: 82,
},
{
label: 'Motion exploration',
progress: 64,
},
{
label: 'Polish & delivery',
progress: 91,
},
]
const galleryImages = [
'https://images.unsplash.com/photo-1545239351-1141bd82e8a6?w=400&h=320&fit=crop&q=80',
'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=400&h=320&fit=crop&q=80',
'https://images.unsplash.com/photo-1545239351-1141bd82e8a6?w=400&h=320&fit=crop&q=80',
'https://images.unsplash.com/photo-1545239351-1141bd82e8a6?w=400&h=320&fit=crop&q=80',
]
export function BentoGridBlock() {
return (
<section className="w-full bg-background px-4 py-16 md:py-24">
<div className="mx-auto max-w-7xl">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
className="mb-12 flex flex-col gap-4 text-center md:mb-16"
>
<Badge className="mx-auto w-fit" variant="secondary">
Motion-first UI Lab
</Badge>
<h2 className="text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl">
Bento grid storytelling with layered motion
</h2>
<p className="mx-auto max-w-2xl text-base text-muted-foreground md:text-lg">
Blend narrative, metrics, and rich visuals into a single responsive layout powered by
smooth Framer Motion transitions.
</p>
</motion.div>
<div className="grid auto-rows-[minmax(200px,auto)] gap-4 md:gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* Story card */}
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.05, ease: 'easeOut' }}
whileHover={{ y: -8 }}
className="col-span-1 sm:col-span-2 lg:row-span-2"
>
<Card className="group flex h-full flex-col justify-between overflow-hidden border-none bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 text-white shadow-lg">
<div className="flex h-full flex-col justify-between p-6 md:p-8">
<div className="space-y-4">
<Badge variant="secondary" className="w-fit border-white/20 bg-white/10 text-white">
Featured case study
</Badge>
<h3 className="text-2xl font-semibold leading-tight md:text-3xl">
Designing delightful product experiences.
</h3>
<p className="text-sm text-white/70 md:text-base">
We craft motion systems that celebrate micro-interactions and elevate usability
across complex product surfaces.
</p>
</div>
<div className="mt-8 flex items-center justify-between gap-4">
<div className="flex -space-x-3">
{avatarImages.map((profile) => (
<div
key={profile.src}
className="relative h-11 w-11 overflow-hidden rounded-full border border-white/20 transition-transform duration-300 group-hover:scale-[1.03]"
>
<img
src={profile.src}
alt={profile.alt}
className="h-full w-full object-cover"
/>
</div>
))}
</div>
<Button
variant="secondary"
className="group/btn gap-2 bg-white/15 text-white hover:bg-white/20"
>
View story
<ArrowUpRight className="h-4 w-4 transition-transform group-hover/btn:translate-x-1" />
</Button>
</div>
</div>
</Card>
</motion.div>
{/* Metrics card */}
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1, ease: 'easeOut' }}
whileHover={{ y: -8 }}
className="col-span-1 sm:col-span-2"
>
<Card className="flex h-full flex-col justify-between border border-border/40 bg-card/80 p-6 backdrop-blur-sm md:p-8">
<div className="flex items-center justify-between">
<Badge variant="secondary" className="w-fit text-primary">
Performance
</Badge>
<motion.div
animate={{ rotate: [0, -8, 0, 8, 0] }}
transition={{ repeat: Infinity, duration: 8, ease: 'easeInOut' }}
>
<Sparkles className="h-5 w-5 text-primary" />
</motion.div>
</div>
<div className="grid gap-4 pt-6 sm:grid-cols-3">
{metrics.map((metric) => (
<div
key={metric.label}
className="rounded-xl border border-border/30 bg-card/60 p-4 text-left shadow-sm"
>
<p className="text-xs uppercase tracking-wide text-muted-foreground">
{metric.label}
</p>
<p className="mt-2 text-2xl font-semibold md:text-3xl">{metric.value}</p>
<p className="mt-1 text-xs text-emerald-500">{metric.caption}</p>
</div>
))}
</div>
</Card>
</motion.div>
{/* Studio image card */}
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.15, ease: 'easeOut' }}
whileHover={{ y: -8 }}
className="col-span-1 sm:col-span-2 lg:row-span-3"
>
<Card className="relative flex h-full overflow-hidden border border-border/40 bg-card p-0">
<img
src="https://images.unsplash.com/photo-1526481280695-3c46999a66f5?auto=format&fit=crop&w=1600&q=80"
alt="Designer workstation with lighting setup"
className="absolute inset-0 h-full w-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-slate-950/80 via-slate-900/30 to-transparent" />
<div className="relative z-10 flex h-full flex-col justify-end space-y-4 p-6 text-white md:p-8">
<Badge variant="secondary" className="w-fit border-white/30 bg-white/15 text-white">
Behind the scenes
</Badge>
<h3 className="text-xl font-semibold md:text-2xl">
Immersive motion prototypes with cinematic lighting
</h3>
<p className="max-w-sm text-sm text-white/75">
Layered light, shadow, and depth cues help teams experience the product as it will
ship—before the first line of code.
</p>
<div className="flex flex-wrap gap-2 pt-2">
{['Micro-interactions', 'Depth cues', 'Narrative flow'].map((tag) => (
<span
key={tag}
className="rounded-full border border-white/20 bg-white/10 px-3 py-1 text-xs uppercase tracking-wide text-white/75"
>
{tag}
</span>
))}
</div>
</div>
</Card>
</motion.div>
{/* Process card */}
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2, ease: 'easeOut' }}
whileHover={{ y: -8 }}
className="col-span-1 sm:col-span-2 lg:row-span-2"
>
<Card className="flex h-full flex-col justify-between border border-border/40 bg-card/80 p-6 shadow-sm backdrop-blur md:p-8">
<div className="space-y-4">
<Badge variant="outline" className="w-fit border-primary/40 text-primary">
Motion sprint
</Badge>
<h3 className="text-xl font-semibold md:text-2xl">
From first sketch to polished prototype in seven days
</h3>
<p className="text-sm text-muted-foreground md:text-base">
We compress discovery, exploration, and refinement into a focused week-long sprint
so your team can feel the flow of the final experience sooner.
</p>
</div>
<div className="space-y-4 pt-6">
{processSteps.map((step) => (
<div key={step.label} className="space-y-2">
<div className="flex items-center justify-between text-xs uppercase tracking-wide text-muted-foreground">
<span>{step.label}</span>
<span>{step.progress}%</span>
</div>
<div className="h-2 w-full overflow-hidden rounded-full bg-muted">
<motion.div
initial={{ width: 0 }}
animate={{ width: step.progress + '%' }}
transition={{ duration: 0.8, ease: 'easeOut', delay: 0.25 }}
className="h-full rounded-full bg-primary/90"
/>
</div>
</div>
))}
</div>
<Button variant="ghost" className="mt-6 justify-start gap-2 px-0 text-sm text-primary hover:text-primary">
<PlayCircle className="h-4 w-4" />
Play walkthrough
</Button>
</Card>
</motion.div>
{/* Video showcase card */}
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.25, ease: 'easeOut' }}
whileHover={{ y: -8 }}
className="col-span-1 sm:col-span-2"
>
<Card className="group relative flex h-full overflow-hidden border border-border/40 bg-gradient-to-br from-primary/10 via-background to-background p-0 shadow-sm">
<div className="relative h-full w-full">
<img
src="https://images.unsplash.com/photo-1611162617474-5b21e879e113?w=800&h=400&fit=crop&q=80"
alt="Motion design workspace"
className="absolute inset-0 h-full w-full object-cover opacity-20 transition-opacity duration-500 group-hover:opacity-30"
/>
<div className="relative z-10 flex h-full flex-col justify-between p-6 md:p-8">
<div className="space-y-4">
<div className="flex items-center gap-3">
<Badge variant="secondary" className="w-fit">
Motion showcase
</Badge>
<motion.div
animate={{ scale: [1, 1.1, 1] }}
transition={{ repeat: Infinity, duration: 2, ease: 'easeInOut' }}
className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/20"
>
<PlayCircle className="h-4 w-4 text-primary" />
</motion.div>
</div>
<h3 className="text-xl font-semibold md:text-2xl">
Watch our latest animation breakdown
</h3>
<p className="max-w-md text-sm text-muted-foreground md:text-base">
A 3-minute deep dive into how we choreograph timing curves, orchestrate
transitions, and build fluid component interactions.
</p>
</div>
<div className="flex items-center justify-between pt-4">
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span className="rounded-full bg-muted px-3 py-1">3:42 min</span>
<span className="rounded-full bg-muted px-3 py-1">4.2K views</span>
</div>
<Button size="sm" className="gap-2">
Watch now
<PlayCircle className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</Card>
</motion.div>
{/* Gallery card */}
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3, ease: 'easeOut' }}
whileHover={{ y: -8 }}
className="col-span-1 sm:col-span-2"
>
<Card className="flex h-full flex-col justify-between border border-border/40 bg-card/80 p-6 shadow-sm backdrop-blur md:p-8">
<div className="space-y-3">
<Badge variant="outline" className="w-fit text-muted-foreground">
Visual research
</Badge>
<h3 className="text-lg font-semibold md:text-xl">
Capturing texture, light, and pace for new explorations
</h3>
<p className="text-sm text-muted-foreground md:text-base">
A snapshot of the references that steer our motion language and narrative rhythm.
</p>
</div>
<div className="mt-6 grid grid-cols-2 gap-3">
{galleryImages.map((image, index) => (
<div key={image + '-' + index} className="relative aspect-[4/3] overflow-hidden rounded-xl">
<img
src={image}
alt="Design inspiration board"
className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
</div>
))}
</div>
<Button variant="link" className="mt-6 self-start px-0 text-sm">
Open inspiration archive
</Button>
</Card>
</motion.div>
</div>
</div>
</section>
)
}
How to Use
- 1Install Framer Motion:
npm install framer-motion - 2Set up shadcn/ui: Install shadcn/ui components used in this code. Check the imports in the code above and install the required components (e.g.,
npx shadcn-ui@latest add button card) - 3Copy the code from above
- 4Paste it into your project and customize as needed
- 5Colors are customizable via Tailwind CSS classes. The default theme uses dark mode colors defined in your globals.css file