Dark Bento Grid
Classic 21st.dev dark bento featuring Revenue Command, Sprint Board, Media Vault, Global Edge on pure black with Framer Motion hover, dot micro-texture, and gradient border glow.
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,
TrendingUp,
Video,
} from "lucide-react";
const opsItems: BentoItem[] = [
{
title: "Revenue Command",
meta: "v3.8.0",
description: "Live MRR, expansion, and churn forecasting with anomaly alerts pushed to Slack and email.",
icon: <TrendingUp className="h-4 w-4 text-blue-400" />,
status: "Live",
tags: ["Finance", "Forecast", "Alerts"],
colSpan: 2,
hasPersistentHover: true,
cta: "View dashboard →",
},
{
title: "Sprint Board",
meta: "52 done",
description: "Kanban lanes with priority scoring, dependency mapping, and automated standup digests.",
icon: <CheckCircle className="h-4 w-4 text-emerald-400" />,
status: "Updated",
tags: ["Agile", "Teams"],
cta: "Open board →",
},
{
title: "Media Vault",
meta: "18GB used",
description: "Versioned asset storage with smart compression, CDN purge, and rights metadata.",
icon: <Video className="h-4 w-4 text-purple-400" />,
tags: ["Storage", "CDN", "Assets"],
colSpan: 2,
cta: "Manage files →",
},
{
title: "Global Edge",
meta: "11 regions",
description: "Multi-region workflow execution with geo-routing and sub-200ms failover between POPs.",
icon: <Globe className="h-4 w-4 text-sky-400" />,
status: "Beta",
tags: ["Edge", "Latency"],
cta: "See map →",
},
];
function BentoGrid({ items = opsItems }: 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 DarkBentoFeatures() {
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">Operations hub</p>
<h2 className="mt-2 text-2xl font-semibold tracking-tight text-zinc-50 sm:text-3xl">Built for scaling teams</h2>
<p className="mt-2 text-sm text-zinc-400">NovaFlow powers 2,400+ companies — finance, delivery, assets, and edge in one view.</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,
TrendingUp,
Video,
} from "lucide-react";
const opsItems: BentoItem[] = [
{
title: "Revenue Command",
meta: "v3.8.0",
description: "Live MRR, expansion, and churn forecasting with anomaly alerts pushed to Slack and email.",
icon: <TrendingUp className="h-4 w-4 text-blue-400" />,
status: "Live",
tags: ["Finance", "Forecast", "Alerts"],
colSpan: 2,
hasPersistentHover: true,
cta: "View dashboard →",
},
{
title: "Sprint Board",
meta: "52 done",
description: "Kanban lanes with priority scoring, dependency mapping, and automated standup digests.",
icon: <CheckCircle className="h-4 w-4 text-emerald-400" />,
status: "Updated",
tags: ["Agile", "Teams"],
cta: "Open board →",
},
{
title: "Media Vault",
meta: "18GB used",
description: "Versioned asset storage with smart compression, CDN purge, and rights metadata.",
icon: <Video className="h-4 w-4 text-purple-400" />,
tags: ["Storage", "CDN", "Assets"],
colSpan: 2,
cta: "Manage files →",
},
{
title: "Global Edge",
meta: "11 regions",
description: "Multi-region workflow execution with geo-routing and sub-200ms failover between POPs.",
icon: <Globe className="h-4 w-4 text-sky-400" />,
status: "Beta",
tags: ["Edge", "Latency"],
cta: "See map →",
},
];
function BentoGrid({ items = opsItems }: 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 DarkBentoFeatures() {
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">Operations hub</p>
<h2 className="mt-2 text-2xl font-semibold tracking-tight text-zinc-50 sm:text-3xl">Built for scaling teams</h2>
<p className="mt-2 text-sm text-zinc-400">NovaFlow powers 2,400+ companies — finance, delivery, assets, and edge in one view.</p>
</motion.div>
<BentoGrid />
</div>
</section>
);
}
export { BentoGrid };
Replace (Your Desired Section) and (YOUR HEX CODE COLOR) before pasting into Cursor, Copilot, or ChatGPT.