You Might Also Like
Mega Menu Panel — Free HTML CSS JS Navigation Snippet
Mega Menu Panel · Navigation · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Mega Menu Panel — Full-Width Dropdown with Link Grid and Featured Promo

When a site has more than a handful of destinations, a plain dropdown becomes a long, hard-to-scan list. The mega menu solves this: a full-width panel that drops from the navigation bar with links organised into a grid — each with an icon, title, and description — plus a featured promo panel to spotlight what is new. It is the navigation pattern used by Stripe, GitHub, Atlassian, and most large product sites. This component builds a complete mega menu in HTML, CSS, and vanilla JavaScript, with hover-intent open, click and keyboard support, and a data-driven panel that swaps content per top-level item.
One shared panel, swapped per menu
Rather than a separate dropdown per nav item, there is one .mmp-panel whose content is repainted when you open a different menu. A MENUS object holds the data for each top-level item ("products", "solutions") — an array of grid cells and a feature promo. Opening a menu calls paint(), which builds the cell grid and the featured panel from that data. This keeps the DOM light (one panel, not many) and makes the menu trivially extensible: add a key to MENUS and a trigger with a matching data-menu attribute.
The two-zone layout
The panel is a grid with a flexible multi-column link area on the left (1fr) and a fixed 260px feature panel on the right. The link area is itself a two-column grid of cells, each a flex row with a tinted icon tile (coloured from a --c custom property via color-mix), a bold title, and a muted description — the scannable, explained-links format that makes a mega menu more useful than a bare list. The feature panel is a gradient card with a tag, headline, and link, anchoring the eye and giving you a place to promote a launch or a guide.
Hover-intent open and close
Mega menus that open and slam shut on every stray mouse movement are infuriating. This one uses hover intent: entering a nav item opens its menu immediately, but leaving does not close instantly — a 160ms setTimeout delays the close, and that timer is cleared if the pointer enters the panel (or another trigger) in the meantime. So moving diagonally from the trigger down into the panel keeps it open, while genuinely leaving closes it after a brief grace period. This forgiving timing is the difference between a mega menu that feels smooth and one that feels hostile.
Click and keyboard too
The menu is not hover-only, which would exclude touch and keyboard users. Each trigger is also clickable — clicking toggles its menu open or closed — and the trigger uses stopPropagation so its click does not immediately hit the document handler that closes the menu on outside clicks. That outside-click handler uses e.composedPath().includes(nav) to reliably detect clicks outside the whole nav even as the panel's contents are repainted, and Escape closes the menu from anywhere. Triggers carry aria-haspopup and aria-expanded that flips with state.
The reveal animation and chevron
The panel starts hidden with opacity: 0, translateY(-10px), and pointer-events: none; opening transitions it to visible and nudges it down into place. Because pointer-events are off while closed, the invisible panel never blocks clicks on the page behind it. The active trigger recolours to the accent and its chevron rotates 180 degrees, so it is always clear which menu is open.
Customisation
Edit the MENUS object to define each panel's cells (icon path, colour, title, description) and feature promo (gradient, tag, headline, link). Add a top-level item by adding a <li data-menu="key"> trigger and a MENUS.key entry. Swap the #6366f1 accent and the feature gradients for your brand. On screens under 720px the panel becomes single-column and the grid stacks, so the same menu works on mobile (where you would typically trigger it by click).
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not need to reconstruct the single-shared-panel architecture by inspecting the DOM alone. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why there is one mmpPanel element repainted by paint(openKey) rather than one panel per top-level nav item, and how the 160ms scheduleClose timer combined with clearTimeout on the panel's own mouseenter creates the hover bridge that prevents flicker when the cursor moves diagonally from trigger to panel. The same assistant can help optimize it, for instance asking whether rebuilding the entire grid.innerHTML string on every openMenu call is wasteful compared to caching each menu's rendered HTML the first time it's painted. It is also useful for extending the menu: ask it to add arrow-key navigation between the cells inside an open panel, support a third or fourth top-level mega menu by extending the MENUS object, or make the panel width responsive to the number of cells rather than a fixed 260px feature column. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "mega menu" navigation bar in plain HTML, CSS, and JavaScript with no libraries, using one shared dropdown panel repainted per top-level menu item rather than one panel per item.
Requirements:
- A nav bar with a brand mark, several top-level items where at least two are buttons with aria-haspopup and aria-expanded attributes (identified by a data-menu key), plus a single shared panel element positioned to span the nav.
- A data object keyed by each menu's identifier, where each entry defines an array of link "cells" (icon, accent color, title, description) and a single featured promo block (background, tag, title, link text).
- A paint function that, given a menu key, rebuilds the shared panel's link grid and featured promo entirely from that key's data, so opening a different top-level item swaps the panel's content without creating or destroying any panel DOM nodes.
- Opening a menu must work on both mouseenter (immediate) and click (toggle), and the panel must visually track which trigger is active by recoloring it and rotating its chevron icon.
- Closing on mouseleave must not happen instantly: leaving a trigger or the panel itself must schedule a close after a short delay (roughly 150-200ms), and entering either the panel or any trigger during that delay must cancel the pending close, so moving the cursor diagonally from the trigger down into the panel never causes a flicker.
- A document-level click listener must close the open menu when a click lands outside the whole nav (using a technique that correctly detects "outside" even though the panel's contents are being repainted), and a keydown listener must close the menu on Escape from anywhere on the page.
- The closed panel must be invisible and non-interactive (no pointer events) so it never blocks clicks on the page content beneath it, and the open panel must animate in with a fade and a slight vertical shift.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste the HTML, CSS, and JSA nav bar renders with brand, top-level links (Products, Solutions have dropdowns), and a Get started CTA.
- 2Hover Products or SolutionsA full-width panel drops down with a grid of explained links and a gradient feature promo, and the trigger's chevron rotates.
- 3Move into the panelHover intent keeps it open as you move from the trigger into the panel; leaving entirely closes it after a short grace period.
- 4Click or press EscapeClicking a trigger toggles its menu; clicking outside the nav or pressing Escape closes it.
- 5Edit the menusChange the MENUS object's cells and feature promo per key, and add items via a data-menu trigger plus a matching MENUS entry.
- 6Theme itSwap the accent colour and the feature gradients, and resize-test: under 720px the panel stacks to one column.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Leaving a nav item does not close the menu immediately — it starts a 160ms setTimeout to close. If the pointer enters the panel (or another trigger) within that window, the timer is cleared and the menu stays open. This handles the common case of moving diagonally from the trigger down into the panel, where the cursor briefly leaves the trigger. Without the delay, that movement would close the menu before you reached it.
Add a <li class="mmp-item" data-menu="resources"> with a trigger button to the nav, then add a matching resources key to the MENUS object with its cells array and feature promo. The script wires every .mmp-item[data-menu] automatically and paints from MENUS[key], so no other code changes are needed — the new menu opens, paints, and closes like the others.
Yes. It is not hover-only: each trigger is clickable and toggles its menu, so touch users can tap to open. The trigger uses stopPropagation so its tap does not immediately trigger the outside-click close. Escape closes the menu, triggers carry aria-haspopup and aria-expanded, and on mobile (under 720px) the panel stacks to one column. For full keyboard menu navigation you can add arrow-key handling between cells.
A single panel that repaints its contents keeps the DOM lightweight (one node, not one per top-level item) and means the open/close animation and positioning logic exist once. The paint() function swaps in the right menu's cells and feature from the MENUS data when you open it. This is more maintainable and scales better than duplicating panel markup for every menu.
Store openKey in state and render the panel's cells/feature from a MENUS map keyed by it. Triggers set openKey on click and on mouseenter; mouseleave starts a close timeout you keep in a ref and clear on the panel's mouseenter. Add a document click listener (outside the nav) and an Escape handler in an effect, cleaned up on unmount. Bind .open and aria-expanded to openKey comparisons. The CSS — layout, reveal, color-mix icons — ports unchanged.