Bento Grid — Growth Suite
Growth-suite motion bento with the same dark black theme with Framer Motion animations. Growth Insights, Approval Queue, Brand Library, Multi-Region Mesh tiles with status pills and #tags.
Dependencies
react
framer-motion
lucide-react
npm install framer-motion lucide-react"use client";
import { motion } from "framer-motion";
function cn(...inputs: (string | undefined | null | false | Record<string, boolean>)[]) {
return inputs
.flatMap((x) => {
if (!x) return [];
if (typeof x === "string") return [x];
return Object.entries(x).filter(([, v]) => v).map(([k]) => k);
})
.join(" ");
}
export interface BentoItem {
title: string;
description: string;
icon: React.ReactNode;
status?: string;
tags?: string[];
meta?: string;
cta?: string;
colSpan?: number;
hasPersistentHover?: boolean;
}
interface BentoGridProps {
items?: BentoItem[];
}
import {
CheckCircle,
Globe,
Layers,
LineChart,
} from "lucide-react";
const growthItems: BentoItem[] = [
{
title: "Growth Insights",
meta: "Q1 2026",
description: "Cohort retention, funnel drop-off, and revenue attribution with AI-generated weekly summaries.",
icon: <LineChart className="h-4 w-4 text-blue-400" />,
status: "Live",
tags: ["Cohort", "Forecast", "BI"],
colSpan: 2,
hasPersistentHover: true,
cta: "Open reports →",
},
{
title: "Approval Queue",
meta: "36 pending",
description: "Route contracts, budgets, and vendor requests through multi-step sign-off with audit trails.",
icon: <CheckCircle className="h-4 w-4 text-emerald-400" />,
status: "Updated",
tags: ["Compliance", "Sign-off"],
cta: "Review queue →",
},
{
title: "Brand Library",
meta: "1.2k assets",
description: "Centralized logos, templates, and campaign creatives with auto-resize and CDN delivery.",
icon: <Layers className="h-4 w-4 text-purple-400" />,
tags: ["DAM", "CDN", "Templates"],
colSpan: 2,
cta: "Browse assets →",
},
{
title: "Multi-Region Mesh",
meta: "9 POPs",
description: "Deploy workflows to US, EU, and APAC edge nodes with automatic failover and latency routing.",
icon: <Globe className="h-4 w-4 text-sky-400" />,
status: "Beta",
tags: ["Infrastructure", "Edge"],
cta: "View regions →",
},
];
function BentoGrid({ items = growthItems }: BentoGridProps) {
return (
<div className="grid grid-cols-1 gap-3 p-4 md:grid-cols-3 mx-auto max-w-7xl">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-40px" }}
transition={{ duration: 0.45, delay: index * 0.07, ease: [0.22, 1, 0.36, 1] }}
whileHover={{ y: -6, scale: 1.012 }}
whileTap={{ scale: 0.995 }}
className={cn(
"group relative overflow-hidden rounded-xl border border-white/10 bg-black p-4",
"shadow-[0_2px_12px_rgba(255,255,255,0.02)] transition-shadow duration-300",
"hover:border-white/20 hover:shadow-[0_8px_32px_rgba(255,255,255,0.06)]",
item.colSpan === 2 ? "md:col-span-2" : "col-span-1",
item.hasPersistentHover && "-translate-y-1.5 shadow-[0_8px_32px_rgba(255,255,255,0.06)] border-white/20"
)}
>
<motion.div
className="pointer-events-none absolute inset-0"
initial={false}
animate={{ opacity: item.hasPersistentHover ? 1 : 0 }}
whileHover={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<div className="absolute inset-0 bg-[length:4px_4px] bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.04)_1px,transparent_1px)]" />
</motion.div>
<div className="relative flex flex-col space-y-3">
<div className="flex items-center justify-between">
<motion.div
whileHover={{ scale: 1.08, rotate: 3 }}
transition={{ type: "spring", stiffness: 400, damping: 18 }}
className="flex h-8 w-8 items-center justify-center rounded-lg bg-white/10"
>
{item.icon}
</motion.div>
<span className="rounded-lg bg-white/10 px-2 py-1 text-xs font-medium text-zinc-300 backdrop-blur-sm">
{item.status || "Active"}
</span>
</div>
<div className="space-y-2">
<h3 className="text-[15px] font-medium tracking-tight text-zinc-100">
{item.title}
{item.meta ? (
<span className="ml-2 text-xs font-normal text-zinc-500">{item.meta}</span>
) : null}
</h3>
<p className="text-sm leading-snug text-zinc-400">{item.description}</p>
</div>
<div className="mt-2 flex items-center justify-between">
<div className="flex flex-wrap items-center gap-2 text-xs text-zinc-500">
{item.tags?.map((tag) => (
<motion.span
key={tag}
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.14)" }}
className="rounded-md bg-white/10 px-2 py-1 backdrop-blur-sm"
>
#{tag}
</motion.span>
))}
</div>
<motion.span
initial={{ opacity: 0, x: -6 }}
whileHover={{ opacity: 1, x: 0 }}
className="text-xs text-zinc-400 opacity-0 transition-opacity group-hover:opacity-100"
>
{item.cta || "Explore →"}
</motion.span>
</div>
</div>
<div
className={cn(
"pointer-events-none absolute inset-0 -z-10 rounded-xl bg-gradient-to-br from-transparent via-white/10 to-transparent p-px",
item.hasPersistentHover ? "opacity-100" : "opacity-0 group-hover:opacity-100",
"transition-opacity duration-300"
)}
/>
</motion.div>
))}
</div>
);
}
export function GrowthBentoFeatures() {
return (
<section className="bg-black px-4 py-20 sm:px-6 lg:px-8">
<div className="mx-auto max-w-7xl">
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-10 max-w-2xl"
>
<p className="text-sm font-medium text-zinc-500">Growth workspace</p>
<h2 className="mt-2 text-2xl font-semibold tracking-tight text-zinc-50 sm:text-3xl">Scale go-to-market without the sprawl</h2>
<p className="mt-2 text-sm text-zinc-400">Insights, approvals, assets, and global delivery in one animated grid.</p>
</motion.div>
<BentoGrid />
</div>
</section>
);
}
export { BentoGrid };
Please implement this code in the current project and add it to (Your Desired Section, e.g. homepage hero, pricing page, dashboard). Change the primary accent color to (YOUR HEX CODE COLOR, e.g. #6366F1).
Requirements:
- Use React + Tailwind CSS (match the project's existing setup)
- Install dependencies if needed: npm install framer-motion lucide-react
- Keep the layout responsive on mobile and desktop
- Replace placeholder copy with content that fits the project
The UI code:
"use client";
import { motion } from "framer-motion";
function cn(...inputs: (string | undefined | null | false | Record<string, boolean>)[]) {
return inputs
.flatMap((x) => {
if (!x) return [];
if (typeof x === "string") return [x];
return Object.entries(x).filter(([, v]) => v).map(([k]) => k);
})
.join(" ");
}
export interface BentoItem {
title: string;
description: string;
icon: React.ReactNode;
status?: string;
tags?: string[];
meta?: string;
cta?: string;
colSpan?: number;
hasPersistentHover?: boolean;
}
interface BentoGridProps {
items?: BentoItem[];
}
import {
CheckCircle,
Globe,
Layers,
LineChart,
} from "lucide-react";
const growthItems: BentoItem[] = [
{
title: "Growth Insights",
meta: "Q1 2026",
description: "Cohort retention, funnel drop-off, and revenue attribution with AI-generated weekly summaries.",
icon: <LineChart className="h-4 w-4 text-blue-400" />,
status: "Live",
tags: ["Cohort", "Forecast", "BI"],
colSpan: 2,
hasPersistentHover: true,
cta: "Open reports →",
},
{
title: "Approval Queue",
meta: "36 pending",
description: "Route contracts, budgets, and vendor requests through multi-step sign-off with audit trails.",
icon: <CheckCircle className="h-4 w-4 text-emerald-400" />,
status: "Updated",
tags: ["Compliance", "Sign-off"],
cta: "Review queue →",
},
{
title: "Brand Library",
meta: "1.2k assets",
description: "Centralized logos, templates, and campaign creatives with auto-resize and CDN delivery.",
icon: <Layers className="h-4 w-4 text-purple-400" />,
tags: ["DAM", "CDN", "Templates"],
colSpan: 2,
cta: "Browse assets →",
},
{
title: "Multi-Region Mesh",
meta: "9 POPs",
description: "Deploy workflows to US, EU, and APAC edge nodes with automatic failover and latency routing.",
icon: <Globe className="h-4 w-4 text-sky-400" />,
status: "Beta",
tags: ["Infrastructure", "Edge"],
cta: "View regions →",
},
];
function BentoGrid({ items = growthItems }: BentoGridProps) {
return (
<div className="grid grid-cols-1 gap-3 p-4 md:grid-cols-3 mx-auto max-w-7xl">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-40px" }}
transition={{ duration: 0.45, delay: index * 0.07, ease: [0.22, 1, 0.36, 1] }}
whileHover={{ y: -6, scale: 1.012 }}
whileTap={{ scale: 0.995 }}
className={cn(
"group relative overflow-hidden rounded-xl border border-white/10 bg-black p-4",
"shadow-[0_2px_12px_rgba(255,255,255,0.02)] transition-shadow duration-300",
"hover:border-white/20 hover:shadow-[0_8px_32px_rgba(255,255,255,0.06)]",
item.colSpan === 2 ? "md:col-span-2" : "col-span-1",
item.hasPersistentHover && "-translate-y-1.5 shadow-[0_8px_32px_rgba(255,255,255,0.06)] border-white/20"
)}
>
<motion.div
className="pointer-events-none absolute inset-0"
initial={false}
animate={{ opacity: item.hasPersistentHover ? 1 : 0 }}
whileHover={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<div className="absolute inset-0 bg-[length:4px_4px] bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.04)_1px,transparent_1px)]" />
</motion.div>
<div className="relative flex flex-col space-y-3">
<div className="flex items-center justify-between">
<motion.div
whileHover={{ scale: 1.08, rotate: 3 }}
transition={{ type: "spring", stiffness: 400, damping: 18 }}
className="flex h-8 w-8 items-center justify-center rounded-lg bg-white/10"
>
{item.icon}
</motion.div>
<span className="rounded-lg bg-white/10 px-2 py-1 text-xs font-medium text-zinc-300 backdrop-blur-sm">
{item.status || "Active"}
</span>
</div>
<div className="space-y-2">
<h3 className="text-[15px] font-medium tracking-tight text-zinc-100">
{item.title}
{item.meta ? (
<span className="ml-2 text-xs font-normal text-zinc-500">{item.meta}</span>
) : null}
</h3>
<p className="text-sm leading-snug text-zinc-400">{item.description}</p>
</div>
<div className="mt-2 flex items-center justify-between">
<div className="flex flex-wrap items-center gap-2 text-xs text-zinc-500">
{item.tags?.map((tag) => (
<motion.span
key={tag}
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.14)" }}
className="rounded-md bg-white/10 px-2 py-1 backdrop-blur-sm"
>
#{tag}
</motion.span>
))}
</div>
<motion.span
initial={{ opacity: 0, x: -6 }}
whileHover={{ opacity: 1, x: 0 }}
className="text-xs text-zinc-400 opacity-0 transition-opacity group-hover:opacity-100"
>
{item.cta || "Explore →"}
</motion.span>
</div>
</div>
<div
className={cn(
"pointer-events-none absolute inset-0 -z-10 rounded-xl bg-gradient-to-br from-transparent via-white/10 to-transparent p-px",
item.hasPersistentHover ? "opacity-100" : "opacity-0 group-hover:opacity-100",
"transition-opacity duration-300"
)}
/>
</motion.div>
))}
</div>
);
}
export function GrowthBentoFeatures() {
return (
<section className="bg-black px-4 py-20 sm:px-6 lg:px-8">
<div className="mx-auto max-w-7xl">
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="mb-10 max-w-2xl"
>
<p className="text-sm font-medium text-zinc-500">Growth workspace</p>
<h2 className="mt-2 text-2xl font-semibold tracking-tight text-zinc-50 sm:text-3xl">Scale go-to-market without the sprawl</h2>
<p className="mt-2 text-sm text-zinc-400">Insights, approvals, assets, and global delivery in one animated grid.</p>
</motion.div>
<BentoGrid />
</div>
</section>
);
}
export { BentoGrid };
Replace (Your Desired Section) and (YOUR HEX CODE COLOR) before pasting into Cursor, Copilot, or ChatGPT.