Skip to content

MegaMenu

The site's main navigation on desktop: disclosure buttons that open panels of links, never a menu, so it is announced and operated as links. Closed panels keep their links in the DOM for crawlers. Compose it with MegaMenuBar and a panel shape, MegaMenuFlyout or MegaMenuFullWidth.

import { MegaMenu } from "@evolve-storefront/layout/mega-menu";
Design notes

One panel is open at a time. Following a link, Escape, a click outside and focus leaving the navigation all close it; Escape returns focus to its button.

Preview

FlyoutOpen in Storybook ↗

Usage

The sites' composition, hover and a full-width panel: choosing a category closes the panel, and it stays closed once the hover timers have run out.

<MegaMenu label={t("main")} renderLink={renderLink} openOnHover>
<MegaMenuBar
items={[{ id: "all", label: t("categories"), children: items }]}
renderPanel={(item) => <MegaMenuFullWidth item={item} />}
/>
</MegaMenu>;

Props

PropTypeDescription
labelstring

Accessible name of the navigation, such as "Main".

childrenReactNode

A MegaMenuBar, whose renderPanel picks the panel shape.

renderLink?MegaMenuLinkRenderer
renderPromo?MegaMenuPromoRenderer<unknown>
icons?Readonly<Record<string, ComponentType<SVGProps<SVGSVGElement>>>>

The site's icons by name; without it, no item shows an icon.

openOnHover?boolean

Open a panel on hover as well as on click and keys.

hoverIntent?number

How long the pointer must rest on an item before hover opens it, in ms.

hoverDelay?number

How long the pointer may be away, or crossing toward a panel, in ms.

Any other props go to the root element, like on a plain HTML element.

Change the layout

Pass children to MegaMenu to arrange its parts yourself. Every part is exported from @evolve-storefront/layout/mega-menu.

<MegaMenu label={t("main")} renderLink={renderLink} openOnHover>
<MegaMenuBar
items={[{ id: "all", label: t("categories"), children: items }]}
renderPanel={(item) => <MegaMenuFullWidth item={item} />}
/>
</MegaMenu>;
MegaMenuBarThe row of top-level entries.
PropTypeDescription
itemsreadonly MegaMenuItem[]
secondaryItems?readonly MegaMenuItem[]

The secondary menu, such as service or business, after items at the row's right end.

renderPanel?((item: MegaMenuItem) => ReactNode)

The panel of an item with children: MegaMenuFlyout unless told otherwise.

Any other props go to the root element, like on a plain HTML element.

MegaMenuBrandsA brands panel across the navigation: "All brands (n)", the popular brands as tiles, and every brand A–Z.
PropTypeDescription
itemMegaMenuBrandsItem
total?number

How many brands the all-brands page has, when more than the menu lists.

className?string
MegaMenuFlyoutA panel under its own button: the item's link, its children as columns, and its promo beside them.
PropTypeDescription
itemMegaMenuItem
className?string
MegaMenuFullWidthA panel across the whole navigation for one button, such as "Categories": its children listed on the left, the chosen one's children as links on the right, with that child's promo or else the button's.
PropTypeDescription
itemMegaMenuItem

The button's item; its children are the menu's top level.

className?string

Examples

Open On HoverHover opens a panel once the pointer rests for hoverIntent, and leaving closes it after hoverDelay.
<MegaMenu label={t("main")} openOnHover>{/* … */}</MegaMenu>;
Open in Storybook ↗
Full WidthOne button opening a panel across the whole navigation: its children on the left, the chosen one's children as links on the right.
<MegaMenu label={t("main")} renderLink={renderLink} renderPromo={renderPromo}>
<MegaMenuBar
items={siteMegaMenuItems(site?.headerNavigation, resolveHref)} // [Categories, Brands, …]
renderPanel={(item) => <MegaMenuFullWidth item={item} />}
/>
</MegaMenu>;
Open in Storybook ↗
Childless ItemA top-level item without children is a link, styled as the buttons beside it: on a dark header "Sale" takes the bar's colour, not the page's.
/* Style both kinds of top-level entry together. */
[data-component="mega-menu-trigger"],
[data-component="mega-menu-bar-link"] {
color: var(--color-primary-foreground);
}
Open in Storybook ↗
BrandsBrands as their own panel: "All brands (n)", the popular brands as tiles, then every brand A–Z.
<MegaMenu label={t("main")} renderLink={renderLink}>
<MegaMenuBar
items={[...items, brands]} // brands: { id, label, href: allBrandsPage, children: [{ id, label, href, logo?, popular? }] }
renderPanel={(item) =>
item.id === brands.id ? <MegaMenuBrands item={brands} total={brandCount} /> : <MegaMenuFlyout item={item} />
}
/>
</MegaMenu>;
Open in Storybook ↗
With IconsAn item's icon names a glyph in the site's icons, shown before its label in the bar and the panel.
// items: [{ id, label, icon: "tools", children }, …]
<MegaMenu label={t("main")} renderLink={renderLink} icons={icons}>
<MegaMenuBar items={items} />
</MegaMenu>;
Open in Storybook ↗
From SiteThe header menu and its secondary menu of the site query, through siteMegaMenuItems: a heading without a link opens its panel all the same, and a product entry links to its page.
const { site } = await fetcher(LayoutGetSite, { version });
const resolveHref = (link) => siteLinkHref(link, ({ slug }) => productHref(slug));
const items = siteMegaMenuItems(site?.headerNavigation, resolveHref);
const secondaryItems = siteMegaMenuItems(site?.secondaryNavigation, resolveHref);
<MegaMenu label={t("main")} renderLink={renderLink} renderPromo={(promo) => <MenuPromo promo={promo} />}>
<MegaMenuBar items={items} secondaryItems={secondaryItems} renderPanel={(item) => <MegaMenuFlyout item={item} />} />
</MegaMenu>;
Open in Storybook ↗