# Mega Menu

> Wide dropdown navigation with grouped links and an optional featured card.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/mega-menu
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/mega-menu.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

```bash
npm install lucide-react
```

## Usage

```tsx title="components/mega-menu-demo.tsx"
"use client"

import { BookOpen, Code, Layers, LifeBuoy, Rocket, Shield, Users, Zap } from "lucide-react"

import { MegaMenu, type MegaMenuItem } from "@/components/mega-menu"

const items: MegaMenuItem[] = [
  {
    label: "Products",
    sections: [
      {
        title: "Build",
        links: [
          { label: "Components", description: "Copy-paste UI blocks", href: "#components", icon: <Layers /> },
          { label: "Templates", description: "Full pages to start from", href: "#templates", icon: <Rocket /> },
          { label: "Playground", description: "Edit and preview live", href: "#playground", icon: <Code /> },
        ],
      },
      {
        title: "Ship",
        links: [
          { label: "Performance", description: "Fast by default", href: "#performance", icon: <Zap /> },
          { label: "Security", description: "Audited dependencies", href: "#security", icon: <Shield /> },
        ],
      },
    ],
    featured: { title: "What's new in v2", description: "Themes, templates and 100+ components.", href: "#new" },
  },
  {
    label: "Resources",
    sections: [
      {
        title: "Learn",
        links: [
          { label: "Documentation", description: "Guides and API reference", href: "#docs", icon: <BookOpen /> },
          { label: "Community", description: "Ask questions, share work", href: "#community", icon: <Users /> },
          { label: "Support", description: "Talk to a human", href: "#support", icon: <LifeBuoy /> },
        ],
      },
    ],
  },
  { label: "Pricing", href: "#pricing" },
]

export default function MegaMenuDemo() {
  return (
    <div className="relative h-[340px] w-full max-w-3xl overflow-hidden rounded-2xl border bg-muted/30">
      <MegaMenu
        items={items}
        logo="Atelier"
        actions={
          <button type="button" className="inline-flex h-8 items-center rounded-lg bg-primary px-3 text-xs font-medium text-primary-foreground">
            Sign up
          </button>
        }
      />
      <div className="grid gap-3 p-8">
        <div className="h-5 w-1/2 rounded bg-muted" />
        <div className="h-3 w-3/4 rounded bg-muted/70" />
        <div className="h-3 w-2/3 rounded bg-muted/70" />
      </div>
    </div>
  )
}
```

## Source

```tsx title="components/mega-menu.tsx"
"use client"

import * as React from "react"
import { ChevronDown } from "lucide-react"

import { cn } from "@/lib/utils"

export type MegaMenuLink = { label: string; description?: string; href: string; icon?: React.ReactNode }
export type MegaMenuSection = { title: string; links: MegaMenuLink[] }
export type MegaMenuFeatured = { title: string; description: string; href: string; className?: string }
export type MegaMenuItem = {
  label: string
  /** Plain link when there are no sections */
  href?: string
  sections?: MegaMenuSection[]
  featured?: MegaMenuFeatured
}

export interface MegaMenuProps extends Omit<React.ComponentProps<"nav">, "children"> {
  items: MegaMenuItem[]
  logo?: React.ReactNode
  actions?: React.ReactNode
}

export function MegaMenu({ items, logo, actions, className, ...props }: MegaMenuProps) {
  const [openIndex, setOpenIndex] = React.useState<number | null>(null)
  const root = React.useRef<HTMLElement>(null)
  const idBase = React.useId()

  React.useEffect(() => {
    if (openIndex === null) return
    const onPointerDown = (event: PointerEvent) => {
      if (!root.current?.contains(event.target as Node)) setOpenIndex(null)
    }
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") setOpenIndex(null)
    }
    document.addEventListener("pointerdown", onPointerDown)
    document.addEventListener("keydown", onKeyDown)
    return () => {
      document.removeEventListener("pointerdown", onPointerDown)
      document.removeEventListener("keydown", onKeyDown)
    }
  }, [openIndex])

  const active = openIndex === null ? null : items[openIndex]

  return (
    <nav
      ref={root}
      aria-label="Main"
      onPointerLeave={(event) => {
        if (event.pointerType === "mouse") setOpenIndex(null)
      }}
      className={cn("relative w-full border-b bg-background text-foreground", className)}
      {...props}
    >
      <div className="flex h-14 items-center gap-6 px-4">
        {logo && <div className="shrink-0 font-semibold">{logo}</div>}
        <ul className="flex items-center gap-1">
          {items.map((item, index) => {
            const triggerClass =
              "inline-flex h-9 items-center gap-1 rounded-md px-3 text-sm font-medium transition-colors hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
            return (
              <li key={item.label}>
                {item.sections ? (
                  <button
                    type="button"
                    aria-expanded={openIndex === index}
                    aria-controls={`${idBase}-${index}`}
                    onClick={() => setOpenIndex(openIndex === index ? null : index)}
                    onPointerEnter={(event) => {
                      if (event.pointerType === "mouse") setOpenIndex(index)
                    }}
                    className={cn(triggerClass, openIndex === index && "bg-accent")}
                  >
                    {item.label}
                    <ChevronDown className={cn("size-3.5 transition-transform duration-200", openIndex === index && "rotate-180")} />
                  </button>
                ) : (
                  <a href={item.href ?? "#"} onPointerEnter={() => setOpenIndex(null)} className={triggerClass}>
                    {item.label}
                  </a>
                )}
              </li>
            )
          })}
        </ul>
        {actions && <div className="ml-auto flex items-center gap-2">{actions}</div>}
      </div>

      {active?.sections && (
        <div
          id={`${idBase}-${openIndex}`}
          key={openIndex}
          className="absolute inset-x-0 top-full z-30 animate-in border-b bg-popover text-popover-foreground shadow-lg duration-200 fade-in slide-in-from-top-2"
        >
          <div className="mx-auto grid max-w-5xl gap-8 p-6 md:grid-cols-[repeat(auto-fit,minmax(12rem,1fr))]">
            {active.sections.map((section) => (
              <div key={section.title} className="grid content-start gap-2">
                <h3 className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">{section.title}</h3>
                <ul className="grid gap-1">
                  {section.links.map((link) => (
                    <li key={link.label}>
                      <a
                        href={link.href}
                        className="group flex items-start gap-3 rounded-lg p-2 transition-colors hover:bg-accent focus-visible:bg-accent focus-visible:outline-none"
                      >
                        {link.icon && (
                          <span className="flex size-8 shrink-0 items-center justify-center rounded-md border bg-background text-muted-foreground transition-colors group-hover:text-foreground [&_svg]:size-4">
                            {link.icon}
                          </span>
                        )}
                        <span className="grid gap-0.5">
                          <span className="text-sm font-medium">{link.label}</span>
                          {link.description && <span className="text-xs text-muted-foreground">{link.description}</span>}
                        </span>
                      </a>
                    </li>
                  ))}
                </ul>
              </div>
            ))}
            {active.featured && (
              <a
                href={active.featured.href}
                className={cn(
                  "flex min-h-40 flex-col justify-end rounded-xl bg-gradient-to-br from-indigo-500 to-fuchsia-500 p-4 text-white focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none",
                  active.featured.className
                )}
              >
                <span className="text-sm font-semibold">{active.featured.title}</span>
                <span className="mt-1 text-xs text-white/80">{active.featured.description}</span>
              </a>
            )}
          </div>
        </div>
      )}
    </nav>
  )
}
```
