Web InnoventixFreeCode

Mega Cols Menu

Original · free

multi-column mega menu with featured

byWeb InnoventixReact + Tailwind
menumegacolsmenus
Open

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.json
menu-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 &mdash; 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">
              &darr;
            </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 quote

Similar components

Browse all →