Navigation Menu

Accessible navigation menu with links and sub-navigation. Install Navigation Menu from the VLLNT UI registry with the shadcn CLI.

Report a bug

Accessible navigation menu with links and sub-navigation. Part of the navigation family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

Add Navigation Menu to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/navigation-menu.json

Source

"use client"; import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"; import { cva } from "class-variance-authority"; import { ChevronDown } from "lucide-react"; import { cn } from "../../lib/utils"; const NavigationMenu = ({ children, className, ref, ...props }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root> & { ref?: React.Ref<React.ComponentRef<typeof NavigationMenuPrimitive.Root>>; }) => ( <NavigationMenuPrimitive.Root className={cn( "relative z-10 flex max-w-max flex-1 items-center justify-center", className, )} ref={ref} {...props} > {children} <NavigationMenuViewport /> </NavigationMenuPrimitive.Root> ); NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; const NavigationMenuList = ({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List> & { ref?: React.Ref<React.ComponentRef<typeof NavigationMenuPrimitive.List>>; }) => ( <NavigationMenuPrimitive.List className={cn( "group flex flex-1 list-none items-center justify-center space-x-1", className, )} ref={ref} {...props} /> ); NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; const NavigationMenuItem = NavigationMenuPrimitive.Item; const navigationMenuTriggerStyle = cva( "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50", ); const NavigationMenuTrigger = ({ children, className, ref, ...props }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger> & { ref?: React.Ref<React.ComponentRef<typeof NavigationMenuPrimitive.Trigger>>; }) => ( <NavigationMenuPrimitive.Trigger className={cn(navigationMenuTriggerStyle(), "group", className)} ref={ref} {...props} > {children}{" "} <ChevronDown aria-hidden="true" className="relative top-[1px] ml-1 size-3 transition duration-200 group-data-[state=open]:rotate-180" /> </NavigationMenuPrimitive.Trigger> ); NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName; const NavigationMenuContent = ({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content> & { ref?: React.Ref<React.ComponentRef<typeof NavigationMenuPrimitive.Content>>; }) => ( <NavigationMenuPrimitive.Content className={cn( "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out 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 md:absolute md:w-auto", className, )} ref={ref} {...props} /> ); NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName; const NavigationMenuLink = NavigationMenuPrimitive.Link; const NavigationMenuViewport = ({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport> & { ref?: React.Ref<React.ComponentRef<typeof NavigationMenuPrimitive.Viewport>>; }) => ( <div className={cn("absolute left-0 top-full flex justify-center")}> <NavigationMenuPrimitive.Viewport className={cn( "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]", className, )} ref={ref} {...props} /> </div> ); NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName; const NavigationMenuIndicator = ({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator> & { ref?: React.Ref<React.ComponentRef<typeof NavigationMenuPrimitive.Indicator>>; }) => ( <NavigationMenuPrimitive.Indicator className={cn( "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in", className, )} ref={ref} {...props} > <div className="relative top-[60%] size-2 rotate-45 rounded-tl-sm bg-border shadow-md" /> </NavigationMenuPrimitive.Indicator> ); NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName; export { NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle, NavigationMenuViewport, };

Stories

Explore every variant and state in the interactive Storybook:

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Dependencies

  • @vllnt/ui@^0.3.0