Component

Mega menu nav

A navigation bar with a mega menu, built on native <details>. HTML, CSS and JavaScript — no framework, no build step, no external request.

Original scale

The component as it was drawn. Hover over "Customers": this is the exact state of the original visual, bar the accent colour. The background is a solid black, because the surface is a translucent white veil — on white it would not read.

In context

The bar as a sticky header, with content passing behind it. Scroll the area: the white veil and the backdrop blur are of no use on a flat fill, everything happens when something moves underneath.

What passes behind

The bar's veil lets through 89% of what lies underneath, and the backdrop blur scrambles the rest. Two settings, a single token each: --dn-surface and --dn-blur.

On a solid background, a white veil at 11% gives exactly #1c1c1c — the colour measured on the original visual. It is the same value, arrived at differently.

Without backdrop-filter, the stylesheet falls back on that flat fill rather than leaving a dirty veil.

Keep scrolling: the bar stays, the background changes.

Variants

Two variants ship with the component. Not one rule is duplicated: they change tokens and nothing else. .mega-menu-nav--compact brings the bar down to form density, .mega-menu-nav--light flips the veil for a light page. This one also carries data-dn-hover="off": it only opens on a click.

Opening on hover

On a device with a fine pointer, moving onto a trigger opens the menu; leaving the bar closes it. Two delays make this usable, and they are CSS variables: --dn-hover-in (90 ms) stops the bar unfolding under a pointer that is only crossing it, and --dn-hover-out (220 ms) covers the journey between the bar and the panel — that space belongs to neither of them, so without it the panel would vanish along the way. Moving from one menu to another, on the other hand, is instant.

Hover replaces nothing: a click still toggles, so does the keyboard, a touch screen is not concerned ((hover: hover) and (pointer: fine)), and as long as focus is working inside the panel the mouse does not close it under the fingers of whoever is using it. If you want none of it: data-dn-hover="off" on the bar.

The staggered entrance

The panel arrives in one piece, its content follows as a wave — the header strip, then each card, left to right and top to bottom. Everything has settled in 370 ms. Three variables set it: --dn-stagger-base (when it starts), --dn-stagger-step (the gap between two blocks; at 0 everything arrives together) and --dn-stagger-rise (the distance travelled).

The trap is worth knowing: written in pure CSS with @starting-style, this entrance only played once per page load. Closing a <details> does not take its content out of the render, the browser hides it with content-visibility — as far as CSS is concerned the panel never stopped existing, so @starting-style, which only fires on the first render, never fires again. A @keyframes behaves the same way. So the script sets the starting state by hand, for the length of one frame, under data-dn-opening.

Without JavaScript

The component enhances <details> elements. Switch the script off: the menus still open, from the keyboard as from the mouse, and the name attribute keeps them mutually exclusive. What the script adds — opening on hover, the stagger replayed on every opening, the closing animation, Escape, clicking outside, the arrow keys — is comfort, not function.

Open the same bar without the script →

The native event still fires

The component does not invent an event system of its own: it drives the open attribute of <details>, so the browser's toggle fires as usual. The log below listens to that and nothing else — it knows nothing about Mega menu nav.

Open a menu to see the event.