Installation
CLI
npx shadcn@latest add "https://eo-n.vercel.app/r/navigation-menu"
Manual
Copy and paste the following code into your project.
"use client";
import * as React from "react";
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui-components/react/navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils";
interface NavigationMenuProps
extends React.ComponentProps<typeof NavigationMenuPrimitive.Root> {
contentProps?: Omit<
React.ComponentProps<typeof NavigationMenuPrimitive.Positioner>,
"render"
>;
}
function NavigationMenu({
className,
children,
contentProps,
...props
}: NavigationMenuProps) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
className={cn(
"group/navigation-menu relative flex max-w-max items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport contentProps={contentProps} />
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn(
"flex flex-1 list-none items-center justify-center gap-1",
className
)}
{...props}
/>
);
}
function NavigationMenuItem({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("relative", className)}
{...props}
/>
);
}
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[popup-open]:hover:bg-accent data-[popup-open]:text-accent-foreground data-[popup-open]:focus:bg-accent data-[popup-open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 [&_svg]:top-[1px] [&_svg]:ml-1 [&[data-popup-open]_svg]:rotate-180"
);
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), className)}
{...props}
>
{children}
<NavigationMenuIcon />
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuIcon({
className,
render = <ChevronDownIcon />,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Icon>) {
return (
<NavigationMenuPrimitive.Icon
className={cn(
"pointer-events-none relative size-3.5 shrink-0 transition duration-300",
className
)}
render={render}
{...props}
/>
);
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"size-auto p-4 transition-[opacity,transform,translate] duration-300 ease-out",
"data-[ending-style]:opacity-0 data-[ending-style]:data-[activation-direction=left]:translate-x-[50%] data-[ending-style]:data-[activation-direction=right]:translate-x-[-50%] data-[starting-style]:opacity-0 data-[starting-style]:data-[activation-direction=left]:translate-x-[-50%] data-[starting-style]:data-[activation-direction=right]:translate-x-[50%]",
"**:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className
)}
{...props}
/>
);
}
function NavigationMenuLink({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm no-underline transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
);
}
function NavigationMenuBackdrop({
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Backdrop>) {
return (
<NavigationMenuPrimitive.Backdrop
data-slot="navigation-menu-backdrop"
{...props}
/>
);
}
function NavigationMenuPortal({
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Portal>) {
return (
<NavigationMenuPrimitive.Portal
data-slot="navigation-menu-portal"
{...props}
/>
);
}
interface NavigationMenuViewportProps
extends React.ComponentProps<typeof NavigationMenuPrimitive.Viewport> {
contentProps?: Omit<
React.ComponentProps<typeof NavigationMenuPrimitive.Positioner>,
"render"
>;
}
function NavigationMenuViewport({
className: viewportClassName,
children,
contentProps,
...props
}: NavigationMenuViewportProps) {
const {
sideOffset = 6,
collisionPadding = { top: 5, bottom: 5, left: 20, right: 20 },
className,
...rest
} = contentProps ?? {};
return (
<NavigationMenuPortal>
<NavigationMenuBackdrop />
<NavigationMenuPrimitive.Positioner
data-slot="navigation-menu-positioner"
sideOffset={sideOffset}
collisionPadding={collisionPadding}
className={cn(
"z-50 box-border h-[var(--positioner-height)] w-[var(--positioner-width)] duration-300 ease-out",
className
)}
{...rest}
>
<NavigationMenuPrimitive.Popup
data-slot="navigation-menu-content"
className={cn(
"bg-popover relative h-[var(--popup-height)] w-full rounded-md border shadow transition-all duration-150 ease-out min-[500px]:w-[var(--popup-width)] md:w-[var(--popup-width)]",
"origin-[var(--transform-origin)] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
className
)}
>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"relative h-full w-full overflow-hidden",
viewportClassName
)}
{...props}
>
{children}
</NavigationMenuPrimitive.Viewport>
</NavigationMenuPrimitive.Popup>
</NavigationMenuPrimitive.Positioner>
</NavigationMenuPortal>
);
}
function NavigationSubMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-sub-trigger"
className={cn(className)}
{...props}
>
{children}
</NavigationMenuPrimitive.Trigger>
);
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuTrigger,
NavigationMenuIcon,
NavigationMenuContent,
NavigationMenuLink,
NavigationMenuBackdrop,
NavigationMenuPortal,
NavigationMenuViewport,
navigationMenuTriggerStyle,
NavigationSubMenuTrigger,
};
Update the import paths to match your project setup.
Usage
Import all parts and piece them together.
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Menubar } from "@/components/ui/menubar";
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink>Link</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>