MC Navigation Menu
A navigation menu component for site-wide navigation.
Overview
The mc-navigation-menu is a flexible navigation system for top-level site structure and grouped
links. It is built on the @base-ui/react navigation menu primitives, then styled with
Tailwind CSS v4 and utility variants from class-variance-authority.
As a part of the MicroClub library, this component is designed for instant deployment into your codebase, giving you full ownership of its look and behavior.
Preview
'use client';import * as React from 'react';import Link from 'next/link';import { CircleAlertIcon, CircleCheckIcon, CircleDashedIcon } from 'lucide-react';import { McNavigationMenu, McNavigationMenuContent, McNavigationMenuItem, McNavigationMenuLink, McNavigationMenuList, McNavigationMenuTrigger, navigationMenuTriggerStyle,} from '../ui/mc-navigation-menu';const components: { title: string; href: string; description: string }[] = [ { title: 'Alert Dialog', href: '#alert-dialog', description: 'A modal dialog that interrupts the user with important content and expects a response.', }, { title: 'Hover Card', href: '#hover-card', description: 'For sighted users to preview content available behind a link.', }, { title: 'Progress', href: '#progress', description: 'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.', }, { title: 'Scroll-area', href: '#scroll-area', description: 'Visually or semantically separates content.', }, { title: 'Tabs', href: '#tabs', description: 'A set of layered sections of content—known as tab panels—that are displayed one at a time.', }, { title: 'Tooltip', href: '#tooltip', description: 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.', },];export default function NavigationMenuDemo() { return ( <McNavigationMenu> <McNavigationMenuList> <McNavigationMenuItem> <McNavigationMenuTrigger>Getting started</McNavigationMenuTrigger> <McNavigationMenuContent> <ul className="w-96"> <ListItem href="#docs" title="Introduction"> Re-usable components built with Tailwind CSS. </ListItem> <ListItem href="#installation" title="Installation"> How to install dependencies and structure your app. </ListItem> <ListItem href="#typography" title="Typography"> Styles for headings, paragraphs, lists...etc </ListItem> </ul> </McNavigationMenuContent> </McNavigationMenuItem> <McNavigationMenuItem className="hidden md:flex"> <McNavigationMenuTrigger>Components</McNavigationMenuTrigger> <McNavigationMenuContent> <ul className="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]"> {components.map((component) => ( <ListItem key={component.title} title={component.title} href={component.href}> {component.description} </ListItem> ))} </ul> </McNavigationMenuContent> </McNavigationMenuItem> <McNavigationMenuItem> <McNavigationMenuTrigger>With Icon</McNavigationMenuTrigger> <McNavigationMenuContent> <ul className="grid w-[200px]"> <li> <McNavigationMenuLink render={ <Link href="#" className="flex items-center gap-2"> <CircleAlertIcon /> Backlog </Link> } /> <McNavigationMenuLink render={ <Link href="#" className="flex items-center gap-2"> <CircleDashedIcon /> To Do </Link> } /> <McNavigationMenuLink render={ <Link href="#" className="flex items-center gap-2"> <CircleCheckIcon /> Done </Link> } /> </li> </ul> </McNavigationMenuContent> </McNavigationMenuItem> <McNavigationMenuItem> <McNavigationMenuLink className={navigationMenuTriggerStyle()} render={<Link href="#docs">Docs</Link>} /> </McNavigationMenuItem> </McNavigationMenuList> </McNavigationMenu> );}function ListItem({ title, children, href, ...props}: React.ComponentPropsWithoutRef<'li'> & { href: string }) { return ( <li {...props}> <McNavigationMenuLink render={ <Link href={href}> <div className="flex flex-col gap-1 text-sm"> <div className="leading-none font-medium">{title}</div> <div className="line-clamp-2 text-muted-foreground">{children}</div> </div> </Link> } /> </li> );}Add to Your Project
Deploy the mc-navigation-menu directly to your components/ui directory:
Manual Setup
If you prefer manual control:
import { NavigationMenu as NavigationMenuPrimitive } from '@base-ui/react/navigation-menu';import { cva } from 'class-variance-authority';import { ChevronDownIcon } from 'lucide-react';import { cn } from '@/lib/utils';function McNavigationMenu({ align = 'start', className, children, ...props}: NavigationMenuPrimitive.Root.Props & Pick<NavigationMenuPrimitive.Positioner.Props, 'align'>) { return ( <NavigationMenuPrimitive.Root data-slot="navigation-menu" className={cn( 'group/navigation-menu relative flex max-w-max flex-1 items-center justify-center', className )} {...props} > {children} <McNavigationMenuPositioner align={align} /> </NavigationMenuPrimitive.Root> );}function McNavigationMenuList({ className, ...props}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.List>) { return ( <NavigationMenuPrimitive.List data-slot="navigation-menu-list" className={cn( 'group flex flex-1 list-none items-center justify-evenly gap-1 ring-1 ring-inset ring-border bg-background rounded-md ', className )} {...props} /> );}function McNavigationMenuItem({ className, ...props}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Item>) { return ( <NavigationMenuPrimitive.Item data-slot="navigation-menu-item" className={cn('relative', className)} {...props} /> );}const navigationMenuTriggerStyle = cva( 'group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center paragraph-sm font-medium transition-all outline-none focus-visible:bg-accent focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent data-popup-open:hover:bg-accent data-open:bg-accent/50 data-open:hover:bg-accent data-open:focus:bg-accent data-popup-open:shadow-[inset_0_0_0_2px_var(--border),0_0_0_2px_var(--border)] data-open:shadow-[inset_0_0_0_2px_var(--border),0_0_0_2px_var(--border)] data-popup-open:hover:shadow-[inset_0_0_0_2px_var(--border),0_0_0_2px_var(--border)] data-popup-open:hover:bg-accent px-4 py-2 rounded-md');function McNavigationMenuTrigger({ className, children, ...props}: NavigationMenuPrimitive.Trigger.Props) { return ( <NavigationMenuPrimitive.Trigger data-slot="navigation-menu-trigger" className={cn( navigationMenuTriggerStyle(), 'group', className, 'text-foreground data-popup-open:text-accent-foreground paragraph-sm text-medium' )} {...props} > {children}{' '} <ChevronDownIcon className="relative top-px ml-1 size-3 transition duration-300 group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180" aria-hidden="true" /> </NavigationMenuPrimitive.Trigger> );}function McNavigationMenuContent({ className, ...props }: NavigationMenuPrimitive.Content.Props) { return ( <NavigationMenuPrimitive.Content data-slot="navigation-menu-content" className={cn( 'data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] h-full w-auto p-4 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-lg group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:ring-1 group-data-[viewport=false]/navigation-menu:ring-foreground/10 group-data-[viewport=false]/navigation-menu:duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95', className )} {...props} /> );}function McNavigationMenuPositioner({ className, side = 'bottom', sideOffset = 8, align = 'start', alignOffset = 0, ...props}: NavigationMenuPrimitive.Positioner.Props) { return ( <NavigationMenuPrimitive.Portal> <NavigationMenuPrimitive.Positioner side={side} sideOffset={sideOffset} align={align} alignOffset={alignOffset} className={cn( 'isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0', className )} {...props} > <NavigationMenuPrimitive.Popup className="data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-popover text-popover-foreground ring-1 ring-border transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0"> <NavigationMenuPrimitive.Viewport className="relative size-full overflow-hidden" /> </NavigationMenuPrimitive.Popup> </NavigationMenuPrimitive.Positioner> </NavigationMenuPrimitive.Portal> );}function McNavigationMenuLink({ className, ...props }: NavigationMenuPrimitive.Link.Props) { return ( <NavigationMenuPrimitive.Link data-slot="navigation-menu-link" className={cn( "flex items-center gap-2 rounded-lg px-2 py-3 paragraph-sm transition-all outline-none hover:bg-muted focus:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-1 in-data-[slot=navigation-menu-content]:rounded-md data-active:bg-muted/50 data-active:hover:bg-muted data-active:focus:bg-muted [&_svg:not([class*='size-'])]:size-4", className )} {...props} /> );}function McNavigationMenuIndicator({ className, ...props}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Icon>) { return ( <NavigationMenuPrimitive.Icon data-slot="navigation-menu-indicator" className={cn( 'top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in', className )} {...props} > <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" /> </NavigationMenuPrimitive.Icon> );}export { McNavigationMenu, McNavigationMenuContent, McNavigationMenuIndicator, McNavigationMenuItem, McNavigationMenuLink, McNavigationMenuList, McNavigationMenuTrigger, navigationMenuTriggerStyle, McNavigationMenuPositioner,};Ensure your @/lib/utils helper is available, and confirm next/link is used where you render
links inside menu content.
Usage
import Link from 'next/link';
import {
McNavigationMenu,
McNavigationMenuContent,
McNavigationMenuItem,
McNavigationMenuLink,
McNavigationMenuList,
McNavigationMenuTrigger,
} from '@/components/ui/mc-navigation-menu';
export default function Example() {
return (
<McNavigationMenu>
<McNavigationMenuList>
<McNavigationMenuItem>
<McNavigationMenuTrigger>Docs</McNavigationMenuTrigger>
<McNavigationMenuContent>
<ul className="w-80">
<li>
<McNavigationMenuLink
render={<Link href="/docs/introduction">Introduction</Link>}
/>
</li>
<li>
<McNavigationMenuLink
render={<Link href="/docs/installation">Installation</Link>}
/>
</li>
</ul>
</McNavigationMenuContent>
</McNavigationMenuItem>
</McNavigationMenuList>
</McNavigationMenu>
);
}Examples
Basic Trigger and Content
<McNavigationMenu>
<McNavigationMenuList>
<McNavigationMenuItem>
<McNavigationMenuTrigger>Getting started</McNavigationMenuTrigger>
<McNavigationMenuContent>
<ul className="w-96">
<li>
<McNavigationMenuLink render={<Link href="/docs">Introduction</Link>} />
</li>
<li>
<McNavigationMenuLink render={<Link href="/docs/installation">Installation</Link>} />
</li>
</ul>
</McNavigationMenuContent>
</McNavigationMenuItem>
</McNavigationMenuList>
</McNavigationMenu>Grid Layout for Rich Menus
<McNavigationMenuContent>
<ul className="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{components.map((item) => (
<li key={item.title}>
<McNavigationMenuLink render={<Link href={item.href}>{item.title}</Link>} />
</li>
))}
</ul>
</McNavigationMenuContent>Icon-Based Menu Links
import { CircleAlertIcon, CircleCheckIcon, CircleDashedIcon } from 'lucide-react';
<McNavigationMenuContent>
<ul className="grid w-[200px]">
<li>
<McNavigationMenuLink
render={
<Link href="#" className="flex-row items-center gap-2">
<CircleAlertIcon />
Backlog
</Link>
}
/>
<McNavigationMenuLink
render={
<Link href="#" className="flex-row items-center gap-2">
<CircleDashedIcon />
To Do
</Link>
}
/>
<McNavigationMenuLink
render={
<Link href="#" className="flex-row items-center gap-2">
<CircleCheckIcon />
Done
</Link>
}
/>
</li>
</ul>
</McNavigationMenuContent>;Direct Link Styled as a Trigger
import { navigationMenuTriggerStyle } from '@/components/ui/mc-navigation-menu';
<McNavigationMenuItem>
<McNavigationMenuLink
className={navigationMenuTriggerStyle()}
render={<Link href="/docs">Docs</Link>}
/>
</McNavigationMenuItem>;API Reference
Components and Utilities
| Export | Type | Default | Description |
|---|---|---|---|
McNavigationMenu | Root component | align="start" | Main container that renders the menu root and a default positioner. |
McNavigationMenuList | Component | undefined | Wraps top-level menu items in a horizontal list container. |
McNavigationMenuItem | Component | undefined | Represents one trigger/content pair or standalone navigation node. |
McNavigationMenuTrigger | Component | undefined | Interactive trigger that opens content and includes a rotating chevron icon. |
McNavigationMenuContent | Component | undefined | Popup content region with open/close and directional motion styles. |
McNavigationMenuLink | Component | undefined | Styled link primitive that supports Base UI polymorphic render usage. |
McNavigationMenuPositioner | Component | side="bottom", sideOffset={8}, align="start", alignOffset={0} | Controls popup placement and viewport sizing behavior. |
McNavigationMenuIndicator | Component | undefined | Optional indicator icon for the active menu item. |
navigationMenuTriggerStyle | CVA utility | undefined | Utility function returning trigger class names for reuse on custom links. |
All other compatible props from @base-ui/react/navigation-menu primitives are supported on each
corresponding wrapper component.