React Navigation Menu Component Examples
Drop-in React NavigationMenu examples: product header, dashboard nav, mega menu, icons with descriptions, and mobile drawer pairing. TypeScript.
A site header lives or dies by its navigation. The NavigationMenu component in Drivn renders a horizontal <nav> with a list of triggers, each opening an absolutely positioned panel of links on hover or focus. The open/close runs through pure CSS — a group-hover/item and group-focus-within/item selector pair flips opacity and visibility on the panel — so there is no React state controller, no useEffect, no portal. The only client-side code in the file is a small onPointerDown handler on the trigger that dismisses focus on a second tap for touch devices.
This page collects the patterns that ship in real product headers and dashboards. Each example imports the component from @/components/ui/navigation-menu — the path the Drivn CLI installs under — and uses the dot-notation API of NavigationMenu, NavigationMenu.List, NavigationMenu.Item, NavigationMenu.Trigger, NavigationMenu.Content, and NavigationMenu.Link so the whole tree composes through a single import. Every snippet is TypeScript because Drivn ships TypeScript-only.
The examples below cover a product header with two dropdowns and a direct link, a dashboard menu with icons, a content panel with link descriptions, a wide mega-menu layout, and a responsive pattern that swaps to a Drawer at the mobile breakpoint. The full component reference and props table live on the NavigationMenu docs page, and the shadcn/ui comparison sits on Drivn vs shadcn NavigationMenu.
Product header with a dropdown and a direct link
The most common shape is a top bar with one or two dropdown items plus a few direct links — "Components" opens a panel, "Docs" navigates straight to a page. Mix the two by giving the dropdown item a NavigationMenu.Trigger plus NavigationMenu.Content and giving the direct-link item a single NavigationMenu.Link with no trigger or content. The Item wrapper sits in both cases and keeps the gap and alignment consistent across the row.
The Trigger renders a button with a ChevronDown icon that rotates 180 degrees on hover or focus through the group-hover/item:rotate-180 and group-focus-within/item:rotate-180 selectors on the icon. The Content panel appears absolutely positioned at left-0 top-full directly under the item, which means the panel anchors to its own trigger rather than to a shared viewport. Use this pattern when each dropdown is independent and the dropdowns do not need to share a morphing surface. For a single-trigger menu without the panel layout, use the Dropdown component instead.
1 import { NavigationMenu } from "@/components/ui/navigation-menu" 2 import { LayoutGrid, MessageSquare } from "lucide-react" 3 4 export default function Header() { 5 return ( 6 <NavigationMenu> 7 <NavigationMenu.List> 8 <NavigationMenu.Item> 9 <NavigationMenu.Trigger> 10 Components 11 </NavigationMenu.Trigger> 12 <NavigationMenu.Content> 13 <NavigationMenu.Link href="/docs/components/card"> 14 <LayoutGrid /> 15 <span className="font-medium">Card</span> 16 </NavigationMenu.Link> 17 <NavigationMenu.Link href="/docs/components/dialog"> 18 <MessageSquare /> 19 <span className="font-medium">Dialog</span> 20 </NavigationMenu.Link> 21 </NavigationMenu.Content> 22 </NavigationMenu.Item> 23 <NavigationMenu.Item> 24 <NavigationMenu.Link href="/docs"> 25 Docs 26 </NavigationMenu.Link> 27 </NavigationMenu.Item> 28 </NavigationMenu.List> 29 </NavigationMenu> 30 ) 31 }
Links with descriptions
When the menu lists destinations that need a short description — "Installation: Get up and running in minutes", "Theming: Customize colors and design tokens" — stack the title and the description vertically inside the Link. Pass className="flex-col gap-0.5" to the NavigationMenu.Link so the default horizontal flex gap-3 flips to a column, then render a <span className="font-medium"> for the title and a <p className="text-xs text-muted-foreground"> for the description. The gap-0.5 keeps the title and the supporting line tight against each other while the py-2.5 from the base styles.link gives breathing room between rows.
This pattern reads like the Vercel and Linear marketing-site headers — wider panels with two-line links that orient the reader without forcing them to click through to find out what the destination is about. Combine it with the icon pattern from the previous section by adding the icon as the first child and wrapping the title plus description in a <div className="flex-1"> so the icon sits on the left and the text stack sits on the right. For tooltip-style descriptions that appear on hover instead of inside the panel, use the Tooltip component on a plain NavigationMenu.Link instead.
1 import { NavigationMenu } from "@/components/ui/navigation-menu" 2 3 export default function GettingStarted() { 4 return ( 5 <NavigationMenu> 6 <NavigationMenu.List> 7 <NavigationMenu.Item> 8 <NavigationMenu.Trigger>Getting Started</NavigationMenu.Trigger> 9 <NavigationMenu.Content> 10 <NavigationMenu.Link href="/docs/installation" className="flex-col gap-0.5"> 11 <span className="font-medium">Installation</span> 12 <p className="text-xs text-muted-foreground"> 13 Get up and running in minutes 14 </p> 15 </NavigationMenu.Link> 16 <NavigationMenu.Link href="/docs/theming" className="flex-col gap-0.5"> 17 <span className="font-medium">Theming</span> 18 <p className="text-xs text-muted-foreground"> 19 Customize colors and design tokens 20 </p> 21 </NavigationMenu.Link> 22 </NavigationMenu.Content> 23 </NavigationMenu.Item> 24 </NavigationMenu.List> 25 </NavigationMenu> 26 ) 27 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
The default open mechanism is group-hover/item plus group-focus-within/item on the parent Item, both pure CSS. To switch to click-only, lift the Content visibility into React state — wrap the Item in a small client component, track an open boolean in useState, and toggle it from a onClick handler on the Trigger. Replace the group-hover/item:opacity-100 rule in the local @/components/ui/navigation-menu.tsx source with a conditional className like open && "opacity-100 visible". The edit lives in your repo because Drivn copies the component source on install.
The directive exists because the Trigger runs an onPointerDown handler to dismiss focus when the user taps a trigger a second time on touch devices — without that handler, focus stays on the trigger and the group-focus-within/item rule keeps the panel open even after the tap. If you do not need the touch-tap-to-close behavior, delete the onPointerDown handler from the Trigger and remove the "use client" directive at the top of the file. The rest of the component is server-renderable.
Yes. The default NavigationMenu.Link renders a native <a> tag with the styled className applied. To swap to Next.js routing, edit the local @/components/ui/navigation-menu.tsx file and replace the <a> element in the Link function with import Link from "next/link" and <Link href={href} className={cn(styles.link, className)} {...props}>. The props spread keeps the href typing intact, and every existing call site continues to work because the dot-notation API does not change.
The default is min-w-[220px] from the styles.content class string, which fits a single column of links with reasonable padding. For mega-menu layouts override the className at the call site with min-w-[480px] or min-w-[640px] and pair it with a grid grid-cols-2 or grid-cols-3 layout. For panels with descriptive sub-text under each link, 320 to 400 pixels reads comfortably. The panel anchors to left-0 top-full of its item, so very wide panels under right-side triggers may need lg:left-auto lg:right-0 to keep the panel inside the viewport.
No. The keyboard model is native tab order — Tab moves to the next focusable element on the page, which is the next Trigger button. To match the arrow-key menu pattern from the WAI-ARIA "menubar" role, add an onKeyDown handler to the NavigationMenu.List that intercepts ArrowLeft and ArrowRight and shifts focus between sibling triggers. The local source file is forty lines, so the edit is scoped to one component file rather than a runtime override. For application command menus where arrow navigation is the expected affordance, the shadcn/ui NavigationMenu wraps Radix and ships the pattern out of the box.

