Mega Cols Menu
Original · freemulti-column mega menu with featured
byWeb InnoventixReact + Tailwind
menumegacolsmenus
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/menu-mega-cols.jsonmenu-mega-cols.tsx
"use client";
import {
useState,
useRef,
useEffect,
type ReactNode,
type KeyboardEvent as ReactKeyboardEvent,
type FocusEvent as ReactFocusEvent,
} from "react";
import { motion, AnimatePresence, useReducedMotion } from "motion/react";
/* ---------------------------------- icons --------------------------------- */
type IconName =
| "bolt"
| "cpu"
| "box"
| "clock"
| "database"
| "sparkles"
| "archive"
| "refresh"
| "traces"
| "chart"
| "search"
| "bell"
| "cart"
| "sliders"
| "layers"
| "users"
| "rocket"
| "book"
| "code"
| "terminal"
| "template"
| "chevron"
| "arrow"
| "play";
const ICON_PATHS: Record<IconName, ReactNode> = {
bolt: <path d="M13 2 4 14h6l-1 8 9-12h-6l1-8Z" />,
cpu: (
<>
<rect x="6" y="6" width="12" height="12" rx="2" />
<rect x="9.5" y="9.5" width="5" height="5" rx="1" />
<path d="M9 3v3M15 3v3M9 18v3M15 18v3M3 9h3M3 15h3M18 9h3M18 15h3" />
</>
),
box: (
<>
<path d="M12 3 4 7v10l8 4 8-4V7l-8-4Z" />
<path d="M4 7l8 4 8-4M12 11v10" />
</>
),
clock: (
<>
<circle cx="12" cy="12" r="9" />
<path d="M12 7.5V12l3 2" />
</>
),
database: (
<>
<ellipse cx="12" cy="5.5" rx="7.5" ry="3" />
<path d="M4.5 5.5v13c0 1.66 3.36 3 7.5 3s7.5-1.34 7.5-3v-13" />
<path d="M4.5 12c0 1.66 3.36 3 7.5 3s7.5-1.34 7.5-3" />
</>
),
sparkles: (
<>
<path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9L12 3Z" />
<path d="M18.5 15v3M17 16.5h3" />
</>
),
archive: (
<>
<rect x="3.5" y="4" width="17" height="4" rx="1" />
<path d="M5.5 8v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1V8" />
<path d="M10 12h4" />
</>
),
refresh: (
<>
<path d="M20.5 12a8.5 8.5 0 1 1-2.4-5.9" />
<path d="M20.5 4v5h-5" />
</>
),
traces: (
<>
<circle cx="5" cy="18" r="2" />
<circle cx="12" cy="6" r="2" />
<circle cx="19" cy="15" r="2" />
<path d="M6.6 16.4 10.4 7.6M13.7 7.1 17.5 13" />
</>
),
chart: (
<>
<path d="M4 4v15a1 1 0 0 0 1 1h15" />
<path d="M8 16v-4M12 16V8M16 16v-6" />
</>
),
search: (
<>
<circle cx="11" cy="11" r="7" />
<path d="m20.5 20.5-4-4" />
</>
),
bell: (
<>
<path d="M18 8.5a6 6 0 1 0-12 0c0 6-2.5 8-2.5 8h17S18 14.5 18 8.5" />
<path d="M10.5 20.5a2 2 0 0 0 3 0" />
</>
),
cart: (
<>
<circle cx="9.5" cy="20" r="1.4" />
<circle cx="17" cy="20" r="1.4" />
<path d="M3 4h2l2.2 11.2a1 1 0 0 0 1 .8h8.6a1 1 0 0 0 1-.78L20.5 8H6.2" />
</>
),
sliders: (
<>
<path d="M4 7h10M18 7h2M4 12h2M10 12h10M4 17h8M16 17h4" />
<circle cx="16" cy="7" r="2" />
<circle cx="8" cy="12" r="2" />
<circle cx="14" cy="17" r="2" />
</>
),
layers: (
<>
<path d="M12 3 3 8l9 5 9-5-9-5Z" />
<path d="m3 12 9 5 9-5M3 16l9 5 9-5" />
</>
),
users: (
<>
<circle cx="9" cy="8" r="3.2" />
<path d="M3.5 20a5.5 5.5 0 0 1 11 0" />
<path d="M16.5 5.2a3 3 0 0 1 0 5.6M20.5 20a5.5 5.5 0 0 0-3.8-5.2" />
</>
),
rocket: (
<>
<path d="M12 2c3 2.5 4.5 6 4.5 9.5L14 15h-4l-2.5-3.5C7.5 8 9 4.5 12 2Z" />
<path d="M9.5 15 7 17.5M14.5 15 17 17.5" />
<circle cx="12" cy="9" r="1.3" />
</>
),
book: (
<>
<path d="M5 4.5A1.5 1.5 0 0 1 6.5 3H18v14H6.5A1.5 1.5 0 0 0 5 18.5V4.5Z" />
<path d="M5 18.5A1.5 1.5 0 0 0 6.5 20H18" />
</>
),
code: <path d="m8.5 8-4 4 4 4M15.5 8l4 4-4 4M13.5 5l-3 14" />,
terminal: (
<>
<rect x="3.5" y="4.5" width="17" height="15" rx="2" />
<path d="m7.5 9.5 3 2.5-3 2.5M13 15h4" />
</>
),
template: (
<>
<rect x="3.5" y="4.5" width="17" height="15" rx="2" />
<path d="M3.5 9.5h17M9 9.5v10" />
</>
),
chevron: <path d="m6 9 6 6 6-6" />,
arrow: <path d="M5 12h13M12.5 6l6 6-6 6" />,
play: <path d="M8 5.5v13l11-6.5-11-6.5Z" fill="currentColor" stroke="none" />,
};
function Icon({ name, className = "h-5 w-5" }: { name: IconName; className?: string }) {
return (
<svg
viewBox="0 0 24 24"
className={className}
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
{ICON_PATHS[name]}
</svg>
);
}
/* ----------------------------------- data --------------------------------- */
interface MenuLink {
label: string;
desc: string;
href: string;
icon: IconName;
}
interface MenuColumn {
heading: string;
links: MenuLink[];
}
interface Featured {
kind: "demo" | "story" | "code";
tone: "indigo" | "emerald" | "sky";
eyebrow: string;
title: string;
body: string;
cta: string;
href: string;
stat?: string;
statLabel?: string;
code?: string[];
}
interface MegaItem {
id: string;
label: string;
type: "mega";
columns: MenuColumn[];
featured: Featured;
}
interface LinkItem {
id: string;
label: string;
type: "link";
href: string;
}
type NavItem = MegaItem | LinkItem;
const ITEMS: NavItem[] = [
{
id: "products",
label: "Products",
type: "mega",
featured: {
kind: "demo",
tone: "indigo",
eyebrow: "Just shipped",
title: "Verdant Copilot",
body: "Describe your stack in plain English and get reviewable infrastructure-as-code in seconds.",
cta: "Watch the 2-min demo",
href: "#copilot",
},
columns: [
{
heading: "Compute",
links: [
{ label: "Edge Functions", desc: "Run code 40ms from every user", href: "#edge-functions", icon: "bolt" },
{ label: "Serverless GPU", desc: "Autoscaling A100s, billed per second", href: "#gpu", icon: "cpu" },
{ label: "Container Runtime", desc: "Any Docker image, zero cold starts", href: "#containers", icon: "box" },
{ label: "Cron & Queues", desc: "Durable background jobs with retries", href: "#jobs", icon: "clock" },
],
},
{
heading: "Data",
links: [
{ label: "Managed Postgres", desc: "Branch your database like a repo", href: "#postgres", icon: "database" },
{ label: "Vector Store", desc: "Similarity search tuned for RAG", href: "#vectors", icon: "sparkles" },
{ label: "Object Storage", desc: "S3-compatible, zero egress fees", href: "#storage", icon: "archive" },
{ label: "Realtime Sync", desc: "Sub-100ms state on every client", href: "#realtime", icon: "refresh" },
],
},
{
heading: "Observe",
links: [
{ label: "Distributed Tracing", desc: "Follow one request across services", href: "#tracing", icon: "traces" },
{ label: "Metrics", desc: "High-cardinality dashboards, no sampling", href: "#metrics", icon: "chart" },
{ label: "Log Search", desc: "Query billions of lines in seconds", href: "#logs", icon: "search" },
{ label: "Alerting", desc: "Page the right on-call, every time", href: "#alerts", icon: "bell" },
],
},
],
},
{
id: "solutions",
label: "Solutions",
type: "mega",
featured: {
kind: "story",
tone: "emerald",
eyebrow: "Customer story",
title: "How Loopwork ships faster",
body: "A nine-person team scaled to 40M daily requests without hiring an ops engineer.",
cta: "Read the case study",
href: "#loopwork",
stat: "63%",
statLabel: "lower p99 latency",
},
columns: [
{
heading: "By use case",
links: [
{ label: "AI Inference", desc: "Serve models close to your users", href: "#inference", icon: "sparkles" },
{ label: "Real-time Apps", desc: "Multiplayer, chat, and live cursors", href: "#realtime-apps", icon: "bolt" },
{ label: "Commerce", desc: "Storefronts that stay fast at scale", href: "#commerce", icon: "cart" },
{ label: "Internal Tools", desc: "Ship admin panels in an afternoon", href: "#internal", icon: "sliders" },
],
},
{
heading: "By team",
links: [
{ label: "Startups", desc: "A free tier that scales to Series C", href: "#startups", icon: "rocket" },
{ label: "Platform Engineering", desc: "Golden paths your devs will use", href: "#platform", icon: "layers" },
{ label: "Data Teams", desc: "Pipelines without the babysitting", href: "#data", icon: "database" },
{ label: "Agencies", desc: "One account, every client project", href: "#agencies", icon: "users" },
],
},
],
},
{
id: "developers",
label: "Developers",
type: "mega",
featured: {
kind: "code",
tone: "sky",
eyebrow: "Quickstart",
title: "Live in 90 seconds",
body: "Install the CLI and deploy your first function to the edge.",
cta: "Open the docs",
href: "#docs",
code: ["$ npm i -g verdant", "$ verdant deploy", "✓ live in 34 regions"],
},
columns: [
{
heading: "Build",
links: [
{ label: "Documentation", desc: "Guides for every framework", href: "#documentation", icon: "book" },
{ label: "API Reference", desc: "Every endpoint, with examples", href: "#api", icon: "code" },
{ label: "CLI", desc: "Deploy straight from your terminal", href: "#cli", icon: "terminal" },
{ label: "SDKs", desc: "TypeScript, Python, Go, and Rust", href: "#sdks", icon: "box" },
],
},
{
heading: "Ship",
links: [
{ label: "Quickstarts", desc: "Zero to deployed in minutes", href: "#quickstarts", icon: "rocket" },
{ label: "Templates", desc: "Production-ready starter apps", href: "#templates", icon: "template" },
{ label: "Examples", desc: "Copy-paste real-world projects", href: "#examples", icon: "layers" },
{ label: "Changelog", desc: "See what shipped this week", href: "#changelog", icon: "clock" },
],
},
],
},
{ id: "pricing", label: "Pricing", type: "link", href: "#pricing" },
{ id: "enterprise", label: "Enterprise", type: "link", href: "#enterprise" },
];
const TONE: Record<Featured["tone"], string> = {
indigo: "from-indigo-500 via-indigo-600 to-violet-700",
emerald: "from-emerald-500 via-emerald-600 to-sky-700",
sky: "from-sky-500 via-indigo-600 to-violet-700",
};
const COLS: Record<number, string> = {
2: "sm:grid-cols-2",
3: "sm:grid-cols-3",
};
const PANEL_ID = "vmm-mega-panel";
const RING =
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950";
/* -------------------------------- sub-views ------------------------------- */
function PanelLink({ link, onNavigate }: { link: MenuLink; onNavigate: () => void }) {
return (
<a
href={link.href}
onClick={onNavigate}
className={`group flex items-start gap-3 rounded-xl p-2.5 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800 ${RING}`}
>
<span className="mt-0.5 grid h-9 w-9 shrink-0 place-items-center rounded-lg bg-slate-100 text-slate-500 transition-colors group-hover:bg-indigo-500 group-hover:text-white dark:bg-slate-800 dark:text-slate-400">
<Icon name={link.icon} className="h-[18px] w-[18px]" />
</span>
<span className="min-w-0">
<span className="block text-sm font-medium text-slate-900 dark:text-white">{link.label}</span>
<span className="block text-xs leading-snug text-slate-500 dark:text-slate-400">{link.desc}</span>
</span>
</a>
);
}
function FeaturedCard({ data, onNavigate }: { data: Featured; onNavigate: () => void }) {
return (
<a
href={data.href}
onClick={onNavigate}
className={`group relative flex h-full min-h-[220px] flex-col overflow-hidden rounded-2xl bg-gradient-to-br ${TONE[data.tone]} p-5 text-white shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950`}
>
<span
aria-hidden="true"
className="vmm-sheen pointer-events-none absolute inset-y-0 -left-1/3 w-1/3 bg-gradient-to-r from-transparent via-white/25 to-transparent"
/>
<span className="inline-flex items-center gap-1.5 self-start rounded-full bg-white/15 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wide ring-1 ring-inset ring-white/25">
{data.kind === "demo" && <span className="vmm-dot h-1.5 w-1.5 rounded-full bg-white" />}
{data.eyebrow}
</span>
{data.kind === "story" && data.stat && (
<div className="mt-4">
<div className="text-4xl font-bold leading-none">{data.stat}</div>
<div className="mt-1 text-xs font-medium text-white/80">{data.statLabel}</div>
</div>
)}
<h3 className="mt-4 text-lg font-semibold leading-snug">{data.title}</h3>
<p className="mt-1.5 text-sm text-white/85">{data.body}</p>
{data.kind === "code" && data.code && (
<pre className="mt-3 overflow-x-auto rounded-lg bg-slate-950/40 p-3 font-mono text-[11px] leading-relaxed text-white/90 ring-1 ring-inset ring-white/10">
{data.code.join("\n")}
</pre>
)}
<span className="mt-auto inline-flex items-center gap-1.5 pt-4 text-sm font-semibold">
{data.kind === "demo" && <Icon name="play" className="h-4 w-4" />}
{data.cta}
<Icon name="arrow" className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
</span>
</a>
);
}
/* --------------------------------- main ----------------------------------- */
export default function MenuMegaCols() {
const reduce = useReducedMotion();
const [openId, setOpenId] = useState<string | null>(null);
const [mobileOpen, setMobileOpen] = useState(false);
const [mobileSection, setMobileSection] = useState<string | null>(null);
const rootRef = useRef<HTMLDivElement | null>(null);
const triggerRefs = useRef<Record<string, HTMLButtonElement | null>>({});
const closeTimer = useRef<number | null>(null);
const focusPanelRef = useRef(false);
const activeMega = ITEMS.find(
(i): i is MegaItem => i.type === "mega" && i.id === openId
);
const cancelClose = () => {
if (closeTimer.current !== null) {
window.clearTimeout(closeTimer.current);
closeTimer.current = null;
}
};
const scheduleClose = () => {
cancelClose();
closeTimer.current = window.setTimeout(() => setOpenId(null), 130);
};
const openMenu = (id: string) => {
cancelClose();
setOpenId(id);
};
const closeMenu = () => {
cancelClose();
setOpenId(null);
};
const toggleMenu = (id: string) => {
cancelClose();
setOpenId((prev) => (prev === id ? null : id));
};
// move focus into the open panel after keyboard open
useEffect(() => {
if (!openId || !focusPanelRef.current) return;
focusPanelRef.current = false;
const panel = document.getElementById(PANEL_ID);
const first = panel?.querySelector<HTMLElement>('a[href], button');
first?.focus();
}, [openId]);
// close on outside pointer
useEffect(() => {
if (!openId) return;
const onDoc = (e: PointerEvent) => {
const root = rootRef.current;
if (root && !root.contains(e.target as Node)) setOpenId(null);
};
document.addEventListener("pointerdown", onDoc);
return () => document.removeEventListener("pointerdown", onDoc);
}, [openId]);
// cleanup timer
useEffect(() => () => cancelClose(), []);
const onNavKeyDown = (e: ReactKeyboardEvent<HTMLElement>) => {
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
const nav = e.currentTarget;
const nodes = Array.from(nav.querySelectorAll<HTMLElement>("[data-navitem]"));
const idx = nodes.indexOf(document.activeElement as HTMLElement);
if (idx === -1) return;
e.preventDefault();
let next = idx;
if (e.key === "ArrowRight") next = (idx + 1) % nodes.length;
else if (e.key === "ArrowLeft") next = (idx - 1 + nodes.length) % nodes.length;
else if (e.key === "Home") next = 0;
else next = nodes.length - 1;
nodes[next]?.focus();
};
const onTriggerKeyDown = (e: ReactKeyboardEvent<HTMLButtonElement>, id: string) => {
if (e.key === "ArrowDown") {
e.preventDefault();
focusPanelRef.current = true;
openMenu(id);
}
};
const onRootKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {
if (e.key !== "Escape") return;
if (openId) {
const id = openId;
closeMenu();
triggerRefs.current[id]?.focus();
} else if (mobileOpen) {
setMobileOpen(false);
}
};
const onRootBlur = (e: ReactFocusEvent<HTMLDivElement>) => {
const root = rootRef.current;
const next = e.relatedTarget as Node | null;
if (openId && (!next || (root && !root.contains(next)))) setOpenId(null);
};
const triggerClass = (open: boolean) =>
`inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${RING} ` +
(open
? "bg-slate-100 text-indigo-600 dark:bg-slate-800 dark:text-indigo-300"
: "text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white");
const panelMotion = reduce
? {}
: {
initial: { opacity: 0, y: -8 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -8 },
transition: { duration: 0.18, ease: "easeOut" as const },
};
const css = `
@keyframes vmm-dot { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.55); } }
@keyframes vmm-sheen { 0% { transform: translateX(0); } 100% { transform: translateX(520%); } }
.vmm-dot { animation: vmm-dot 1.8s ease-in-out infinite; }
.vmm-sheen { animation: vmm-sheen 4.5s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.vmm-dot, .vmm-sheen { animation: none !important; }
}
`;
return (
<section className="relative w-full overflow-hidden bg-slate-50 py-16 sm:py-20 dark:bg-slate-950">
<style>{css}</style>
{/* atmosphere */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute -top-28 left-1/2 h-72 w-72 -translate-x-1/2 rounded-full bg-indigo-300/30 blur-3xl dark:bg-indigo-500/20" />
<div className="absolute bottom-4 right-6 h-64 w-64 rounded-full bg-violet-300/20 blur-3xl dark:bg-violet-500/10" />
</div>
<div className="relative z-10 mx-auto max-w-6xl px-4 sm:px-6">
<div
ref={rootRef}
className="relative"
onKeyDown={onRootKeyDown}
onBlur={onRootBlur}
onMouseLeave={scheduleClose}
>
{/* bar */}
<header className="relative z-40 flex items-center justify-between gap-4 rounded-2xl border border-slate-200 bg-white/85 px-4 py-3 shadow-sm backdrop-blur-md dark:border-slate-800 dark:bg-slate-900/85">
<a href="#top" className={`flex items-center gap-2.5 rounded-lg py-1 pr-2 ${RING}`}>
<span className="grid h-8 w-8 place-items-center rounded-lg bg-gradient-to-br from-indigo-500 to-violet-600 text-white shadow-sm">
<svg
viewBox="0 0 24 24"
width={18}
height={18}
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M4 5h5l3 9 3-9h5l-6 14h-4L4 5Z" />
</svg>
</span>
<span className="text-base font-semibold tracking-tight text-slate-900 dark:text-white">Verdant</span>
</a>
<nav aria-label="Primary" className="hidden md:block" onKeyDown={onNavKeyDown}>
<ul className="flex items-center gap-0.5">
{ITEMS.map((item) => (
<li key={item.id}>
{item.type === "mega" ? (
<button
ref={(el) => {
triggerRefs.current[item.id] = el;
}}
data-navitem
type="button"
aria-haspopup="true"
aria-expanded={openId === item.id}
aria-controls={PANEL_ID}
onClick={() => toggleMenu(item.id)}
onMouseEnter={() => openMenu(item.id)}
onKeyDown={(e) => onTriggerKeyDown(e, item.id)}
className={triggerClass(openId === item.id)}
>
{item.label}
<Icon
name="chevron"
className={`h-4 w-4 transition-transform duration-200 ${
openId === item.id ? "rotate-180" : ""
}`}
/>
</button>
) : (
<a
data-navitem
href={item.href}
onMouseEnter={closeMenu}
className={triggerClass(false)}
>
{item.label}
</a>
)}
</li>
))}
</ul>
</nav>
<div className="hidden items-center gap-2 md:flex" onMouseEnter={scheduleClose}>
<a
href="#signin"
className={`rounded-lg px-3 py-2 text-sm font-medium text-slate-700 transition-colors hover:text-slate-900 dark:text-slate-300 dark:hover:text-white ${RING}`}
>
Sign in
</a>
<a
href="#start"
className={`rounded-lg bg-slate-900 px-3.5 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200 ${RING}`}
>
Start building
</a>
</div>
{/* mobile toggle */}
<button
type="button"
aria-expanded={mobileOpen}
aria-controls="vmm-mobile"
aria-label={mobileOpen ? "Close navigation menu" : "Open navigation menu"}
onClick={() => {
setMobileOpen((o) => !o);
setOpenId(null);
}}
className={`grid h-10 w-10 place-items-center rounded-lg border border-slate-200 text-slate-700 md:hidden dark:border-slate-700 dark:text-slate-200 ${RING}`}
>
<svg
viewBox="0 0 24 24"
className="h-5 w-5"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
{mobileOpen ? <path d="m6 6 12 12M18 6 6 18" /> : <path d="M4 7h16M4 12h16M4 17h16" />}
</svg>
</button>
</header>
{/* mobile panel */}
{mobileOpen && (
<div
id="vmm-mobile"
className="relative z-30 mt-2 rounded-2xl border border-slate-200 bg-white p-2 shadow-xl md:hidden dark:border-slate-800 dark:bg-slate-900"
>
<ul className="space-y-1">
{ITEMS.map((item) =>
item.type === "link" ? (
<li key={item.id}>
<a
href={item.href}
onClick={() => setMobileOpen(false)}
className={`block rounded-lg px-3 py-2.5 text-sm font-medium text-slate-800 hover:bg-slate-100 dark:text-slate-100 dark:hover:bg-slate-800 ${RING}`}
>
{item.label}
</a>
</li>
) : (
<li key={item.id}>
<button
type="button"
aria-expanded={mobileSection === item.id}
aria-controls={`vmm-m-${item.id}`}
onClick={() =>
setMobileSection((s) => (s === item.id ? null : item.id))
}
className={`flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-sm font-medium text-slate-800 hover:bg-slate-100 dark:text-slate-100 dark:hover:bg-slate-800 ${RING}`}
>
{item.label}
<Icon
name="chevron"
className={`h-4 w-4 text-slate-400 transition-transform duration-200 ${
mobileSection === item.id ? "rotate-180" : ""
}`}
/>
</button>
{mobileSection === item.id && (
<div id={`vmm-m-${item.id}`} className="px-1 pb-2 pt-1">
{item.columns.map((col) => (
<div key={col.heading} className="py-1">
<p className="px-2 py-1 text-[11px] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">
{col.heading}
</p>
{col.links.map((l) => (
<a
key={l.label}
href={l.href}
onClick={() => setMobileOpen(false)}
className={`flex items-center gap-3 rounded-lg px-2 py-2 hover:bg-slate-100 dark:hover:bg-slate-800 ${RING}`}
>
<Icon name={l.icon} className="h-[18px] w-[18px] text-slate-500 dark:text-slate-400" />
<span className="text-sm text-slate-700 dark:text-slate-200">{l.label}</span>
</a>
))}
</div>
))}
<a
href={item.featured.href}
onClick={() => setMobileOpen(false)}
className={`mt-1 block rounded-lg bg-indigo-500 px-3 py-2 text-center text-sm font-semibold text-white hover:bg-indigo-600 ${RING}`}
>
{item.featured.cta}
</a>
</div>
)}
</li>
)
)}
</ul>
<div className="mt-2 grid grid-cols-2 gap-2 border-t border-slate-200 pt-2 dark:border-slate-800">
<a
href="#signin"
onClick={() => setMobileOpen(false)}
className={`rounded-lg border border-slate-200 px-3 py-2 text-center text-sm font-medium text-slate-800 dark:border-slate-700 dark:text-slate-100 ${RING}`}
>
Sign in
</a>
<a
href="#start"
onClick={() => setMobileOpen(false)}
className={`rounded-lg bg-slate-900 px-3 py-2 text-center text-sm font-semibold text-white dark:bg-white dark:text-slate-900 ${RING}`}
>
Start building
</a>
</div>
</div>
)}
{/* desktop mega panel */}
<AnimatePresence>
{activeMega && (
<motion.div
key={activeMega.id}
id={PANEL_ID}
role="region"
aria-label={`${activeMega.label} menu`}
onMouseEnter={cancelClose}
onMouseLeave={scheduleClose}
{...panelMotion}
className="absolute left-0 right-0 top-full z-50 hidden pt-3 md:block"
>
<div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-2xl shadow-slate-900/10 sm:p-5 dark:border-slate-800 dark:bg-slate-900 dark:shadow-black/40">
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
<div className="lg:col-span-8">
<div
className={`grid grid-cols-1 gap-x-4 gap-y-6 ${
COLS[activeMega.columns.length] ?? "sm:grid-cols-2"
}`}
>
{activeMega.columns.map((col) => (
<div key={col.heading}>
<p className="px-2.5 pb-1 text-[11px] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">
{col.heading}
</p>
<ul className="space-y-0.5">
{col.links.map((l) => (
<li key={l.label}>
<PanelLink link={l} onNavigate={closeMenu} />
</li>
))}
</ul>
</div>
))}
</div>
</div>
<div className="lg:col-span-4">
<FeaturedCard data={activeMega.featured} onNavigate={closeMenu} />
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
{/* page context beneath the menu */}
<div className="relative z-10 mx-auto mt-12 max-w-3xl text-center sm:mt-16">
<span className="inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white/60 px-3 py-1 text-xs font-medium text-slate-600 dark:border-slate-800 dark:bg-slate-900/60 dark:text-slate-300">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
Edge cloud, now in 34 regions
</span>
<h1 className="mt-5 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl dark:text-white">
Ship AI-native apps to the edge.
</h1>
<p className="mx-auto mt-4 max-w-xl text-base text-slate-600 dark:text-slate-300">
One platform for compute, data, and observability. Open a menu above — hover, click, or press{" "}
<kbd className="rounded border border-slate-300 bg-slate-100 px-1.5 py-0.5 font-mono text-xs text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200">
↓
</kbd>{" "}
and{" "}
<kbd className="rounded border border-slate-300 bg-slate-100 px-1.5 py-0.5 font-mono text-xs text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200">
Esc
</kbd>{" "}
to explore.
</p>
<div className="mt-7 flex flex-wrap items-center justify-center gap-3">
<a
href="#start"
className={`rounded-xl bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-600/20 transition-colors hover:bg-indigo-500 ${RING}`}
>
Start building free
</a>
<a
href="#demo"
className={`rounded-xl border border-slate-300 px-5 py-2.5 text-sm font-semibold text-slate-800 transition-colors hover:bg-white dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-900 ${RING}`}
>
Book a demo
</a>
</div>
</div>
</div>
</section>
);
}Dependencies
motion
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Dropdown Menu
Originalbasic dropdown menu with items
Dropdown Icons Menu
Originaldropdown with icons and shortcuts

User Menu
Originaluser account dropdown with avatar

Mega Menu
Originalmega menu with grouped links

Context Menu
Originalright-click context menu

Nested Menu
Originaldropdown with nested submenus

Select Menu
Originalcustom single select dropdown

Multiselect Menu
Originalmulti-select with checkable items

Command Menu
Originalcommand palette style menu with search

Actions Menu
Originalactions/kebab menu

Share Menu
Originalshare menu with options

Notifications Menu
Originalnotifications dropdown list

