Skip to content

Header Mega Menu

BlockMantine ≥ 9MantineProvider

Responsive site header with a hover-card feature mega-menu that collapses into a mobile drawer.

Install

One command copies the item's source into your project, where you own and edit it.

Terminal window
npx manteen add @house/header-mega-menu

Dependencies

  • @mantine/core@^9npm
  • @mantine/hooks@^9npm
  • @tabler/icons-react@^3npm

Installing this item also installs @house/mantine-ui-license from the registry.

Preview

Usage

A copy-ready example published by the registry author. Imports use the aliases Manteen configures at init.

header-mega-menu.usage.tsx
import { Text } from "@mantine/core";
import {
IconBook,
IconChartPie3,
IconCode,
IconCoin,
IconFingerprint,
IconNotification,
} from "@tabler/icons-react";
import { HeaderMegaMenu, type HeaderMegaMenuLink } from "@/components/ui/header-mega-menu";
const links: HeaderMegaMenuLink[] = [
{ label: "Home", href: "/" },
{
label: "Features",
href: "/features",
features: [
{
icon: <IconCode size={22} color="var(--mantine-color-blue-6)" />,
title: "Open source",
description: "The full source is on GitHub, under the MIT license",
},
{
icon: <IconCoin size={22} color="var(--mantine-color-blue-6)" />,
title: "Free for everyone",
description: "No paid tier gates any core feature",
},
{
icon: <IconBook size={22} color="var(--mantine-color-blue-6)" />,
title: "Documentation",
description: "Guides and API references for every component",
},
{
icon: <IconFingerprint size={22} color="var(--mantine-color-blue-6)" />,
title: "Security",
description: "SOC 2 Type II audited, with SSO on every plan",
},
{
icon: <IconChartPie3 size={22} color="var(--mantine-color-blue-6)" />,
title: "Analytics",
description: "Usage dashboards and exportable reports",
},
{
icon: <IconNotification size={22} color="var(--mantine-color-blue-6)" />,
title: "Notifications",
description: "Email and webhook alerts for every event",
},
],
},
{ label: "Learn", href: "/learn" },
{ label: "Academy", href: "/academy" },
];
export function SiteHeader() {
return (
<HeaderMegaMenu
logo={
<Text fw={700} size="lg">
Acme
</Text>
}
links={links}
featuresViewAllHref="/features"
onLogin={() => console.log("log in")}
onSignUp={() => console.log("sign up")}
/>
);
}

Props

Author-documented props, carried verbatim from the registry item.

PropTypeDefaultDescription
logoReactNodeRendered at the start of the header bar; nothing renders when omitted.
links*readonly HeaderMegaMenuLink[]Ordered nav links rendered in the header bar and the mobile drawer. At most one entry should set `features`: only the first entry that does renders as the mega-menu (a hover-card dropdown on desktop, a collapsible section on mobile); `features` on any later entry is ignored.
featuresViewAllHrefstringHref for the mega-menu's "View all" link; the link is omitted when unset.
featuresFooterHeadingstring"Get started"Heading shown in the mega-menu's footer callout.
featuresFooterDescriptionstring"Everything you need to explore what's possible and start building."Body copy shown in the mega-menu's footer callout.
featuresFooterActionLabelstring"Get started"Label for the mega-menu footer's action button; clicking it calls `onSignUp`.
loginLabelstring"Log in"Label for the log in button, shown in the header and the mobile drawer.
signUpLabelstring"Sign up"Label for the sign up button, shown in the header and the mobile drawer.
onLogin() => voidCalled when a log in button (header or drawer) is clicked.
onSignUp() => voidCalled when a sign up button (header or drawer) or the mega-menu footer's action button is clicked.

* required

Styling

No public Styles API is declared. Installed CSS remains editable because your project owns the source; internal class names are implementation details, not a customization contract.

Source

2 installable files ship with this item.

registry/mantine-ui/header-mega-menu/header-mega-menu.tsxcomponent · 7.1 kB · tsx
/**
* Adapted from Mantine UI's HeaderMegaMenu at
* ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see
* LICENSES/MANTINE-UI.txt after installation.
*/
import {
Anchor,
Box,
Burger,
Button,
Center,
Collapse,
Divider,
Drawer,
Group,
HoverCard,
ScrollArea,
SimpleGrid,
Text,
ThemeIcon,
UnstyledButton,
useMantineTheme,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconChevronDown } from "@tabler/icons-react";
import type { ReactNode } from "react";
import classes from "./header-mega-menu.module.css";
export interface HeaderMegaMenuFeature {
icon?: ReactNode;
title: string;
description: string;
}
export interface HeaderMegaMenuLink {
label: string;
href: string;
/**
* Renders this link as a hover/tap mega-menu instead of a plain link. Only
* the first entry in `links` that sets `features` is treated as the
* mega-menu; `features` on any later entry is ignored.
*/
features?: readonly HeaderMegaMenuFeature[];
}
export interface HeaderMegaMenuProps {
/** Rendered at the start of the header bar; nothing renders when omitted. */
logo?: ReactNode;
/** Ordered nav links; at most one entry should set `features`. */
links: readonly HeaderMegaMenuLink[];
/** "View all" link inside the mega-menu; the link is omitted when unset. */
featuresViewAllHref?: string;
featuresFooterHeading?: string;
featuresFooterDescription?: string;
featuresFooterActionLabel?: string;
loginLabel?: string;
signUpLabel?: string;
onLogin?: () => void;
onSignUp?: () => void;
}
export function HeaderMegaMenu({
logo,
links,
featuresViewAllHref,
featuresFooterHeading = "Get started",
featuresFooterDescription = "Everything you need to explore what's possible and start building.",
featuresFooterActionLabel = "Get started",
loginLabel = "Log in",
signUpLabel = "Sign up",
onLogin,
onSignUp,
}: HeaderMegaMenuProps) {
const [drawerOpened, { toggle: toggleDrawer, close: closeDrawer }] = useDisclosure(false);
const [linksOpened, { toggle: toggleLinks }] = useDisclosure(false);
const theme = useMantineTheme();
const featuresLink = links.find((link) => link.features);
const featureItems = featuresLink?.features?.map((item) => (
<UnstyledButton className={classes.subLink} key={item.title}>
<Group wrap="nowrap" align="flex-start">
<ThemeIcon size={34} variant="default" radius="md">
{item.icon}
</ThemeIcon>
<div>
<Text size="sm" fw={500}>
{item.title}
</Text>
<Text size="xs" c="dimmed">
{item.description}
</Text>
</div>
</Group>
</UnstyledButton>
));
return (
<>
<header className={classes.header}>
<Group justify="space-between" h="100%">
{logo}
<Group h="100%" gap={0} visibleFrom="sm">
{links.map((link) =>
link === featuresLink ? (
<HoverCard
key={link.label}
width={600}
position="bottom"
radius="md"
shadow="md"
withinPortal
>
<HoverCard.Target>
<UnstyledButton className={classes.link}>
<Center inline>
<Box component="span" mr={5}>
{link.label}
</Box>
<IconChevronDown size={16} color={theme.colors.blue[6]} />
</Center>
</UnstyledButton>
</HoverCard.Target>
<HoverCard.Dropdown style={{ overflow: "hidden" }}>
<Group justify="space-between" px="md">
<Text fw={500}>{link.label}</Text>
{featuresViewAllHref && (
<Anchor href={featuresViewAllHref} fz="xs">
View all
</Anchor>
)}
</Group>
<Divider my="sm" />
<SimpleGrid cols={2} spacing={0}>
{featureItems}
</SimpleGrid>
<div className={classes.dropdownFooter}>
<Group justify="space-between">
<div>
<Text fw={500} fz="sm">
{featuresFooterHeading}
</Text>
<Text size="xs" c="dimmed">
{featuresFooterDescription}
</Text>
</div>
<Button variant="default" onClick={onSignUp}>
{featuresFooterActionLabel}
</Button>
</Group>
</div>
</HoverCard.Dropdown>
</HoverCard>
) : (
<a href={link.href} className={classes.link} key={link.label}>
{link.label}
</a>
),
)}
</Group>
<Group visibleFrom="sm">
<Button variant="default" onClick={onLogin}>
{loginLabel}
</Button>
<Button onClick={onSignUp}>{signUpLabel}</Button>
</Group>
<Burger
opened={drawerOpened}
onClick={toggleDrawer}
hiddenFrom="sm"
aria-label="Toggle navigation"
/>
</Group>
</header>
<Drawer
opened={drawerOpened}
onClose={closeDrawer}
size="100%"
padding="md"
title="Navigation"
hiddenFrom="sm"
zIndex={1000000}
>
<ScrollArea h="calc(100vh - 80px)" mx="-md">
<Divider my="sm" />
{links.map((link) =>
link === featuresLink ? (
<div key={link.label}>
<UnstyledButton
className={classes.link}
onClick={toggleLinks}
aria-expanded={linksOpened}
>
<Center inline>
<Box component="span" mr={5}>
{link.label}
</Box>
<IconChevronDown size={16} color={theme.colors.blue[6]} />
</Center>
</UnstyledButton>
<Collapse expanded={linksOpened}>{featureItems}</Collapse>
</div>
) : (
<a href={link.href} className={classes.link} key={link.label}>
{link.label}
</a>
),
)}
<Divider my="sm" />
<Group justify="center" grow pb="xl" px="md">
<Button variant="default" onClick={onLogin}>
{loginLabel}
</Button>
<Button onClick={onSignUp}>{signUpLabel}</Button>
</Group>
</ScrollArea>
</Drawer>
</>
);
}
registry/mantine-ui/header-mega-menu/header-mega-menu.module.cssstyle · 1.9 kB · css
/*
* Adapted from Mantine UI's HeaderMegaMenu at
* ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see
* LICENSES/MANTINE-UI.txt after installation.
*/
.header {
height: 60px;
padding-left: var(--mantine-spacing-md);
padding-right: var(--mantine-spacing-md);
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
/* Upstream used postcss-preset-mantine's `@mixin hover` and
* `$mantine-breakpoint-sm`; this registry ships plain CSS modules with no
* PostCSS preset, so both are inlined here (`:hover`, and the literal `48em`
* default `sm` breakpoint value — `var()` is invalid inside a media
* condition). See cards-carousel.module.css for the same convention. */
.link {
display: flex;
align-items: center;
height: 100%;
padding-left: var(--mantine-spacing-md);
padding-right: var(--mantine-spacing-md);
text-decoration: none;
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
font-weight: 500;
font-size: var(--mantine-font-size-sm);
}
.link:hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}
@media (max-width: 48em) {
.link {
height: 42px;
width: 100%;
}
}
.subLink {
width: 100%;
padding: var(--mantine-spacing-xs) var(--mantine-spacing-md);
border-radius: var(--mantine-radius-md);
}
.subLink:hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
}
.dropdownFooter {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
margin: calc(var(--mantine-spacing-md) * -1);
margin-top: var(--mantine-spacing-sm);
padding: var(--mantine-spacing-md) calc(var(--mantine-spacing-md) * 2);
padding-bottom: var(--mantine-spacing-xl);
border-top: 1px solid light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
}

Author notes

Adapted from Mantine UI HeaderMegaMenu at ffbf61c559…; feature-grid mock data and the two CTA labels became typed props, the fixed nav links became a consumer-supplied array, and the upstream @mantinex/mantine-logo import (not part of this workspace) became a logo ReactNode prop. Kept as an opinionated block with internal CSS classes rather than a public Styles API, consistent with this catalog's other page-section blocks (authentication-form, stats-grid, table-sort).