Skip to content
Drivn
7 min read

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.

Dashboard nav with icons

A dashboard header often pairs each link with a lucide icon — "Components" with Blocks, "Theming" with Palette, "CLI" with Terminal. The Drivn NavigationMenu.Link accepts any children, so drop the icon component directly as the first child and follow it with a <span className="font-medium"> for the link label. The default styles.link class is flex w-full gap-3 px-3 py-2.5, which gives the icon and label a clean horizontal layout with twelve pixels between them.

This pattern reads naturally in admin tools and product dashboards where the icon set is small and consistent. Keep the icon size at the lucide default (twenty-four pixels) — the gap-3 and py-2.5 values are tuned for that scale. If you want smaller icons, override at the call site with <Blocks className="w-4 h-4" /> rather than editing the styles.link rule, so the override is scoped to the call site rather than affecting every NavigationMenu in the app. Pair this with the Drivn Sidebar component for full-app navigation that combines a header and a vertical rail.

1import { NavigationMenu } from "@/components/ui/navigation-menu"
2import { Blocks, Palette, Terminal } from "lucide-react"
3
4export default function DashboardNav() {
5 return (
6 <NavigationMenu>
7 <NavigationMenu.List>
8 <NavigationMenu.Item>
9 <NavigationMenu.Trigger>Explore</NavigationMenu.Trigger>
10 <NavigationMenu.Content>
11 <NavigationMenu.Link href="/components" className="items-center">
12 <Blocks />
13 <span className="font-medium">Components</span>
14 </NavigationMenu.Link>
15 <NavigationMenu.Link href="/theming" className="items-center">
16 <Palette />
17 <span className="font-medium">Theming</span>
18 </NavigationMenu.Link>
19 <NavigationMenu.Link href="/cli" className="items-center">
20 <Terminal />
21 <span className="font-medium">CLI</span>
22 </NavigationMenu.Link>
23 </NavigationMenu.Content>
24 </NavigationMenu.Item>
25 </NavigationMenu.List>
26 </NavigationMenu>
27 )
28}

Mega-menu layout with two columns

For documentation hubs or product surfaces with many destinations, swap the default min-w-[220px] content panel for a wider layout with two columns of links. Override the NavigationMenu.Content className with min-w-[480px] grid grid-cols-2 gap-2 so the panel grows to 480 pixels wide and lays the children out in a two-column grid. Each child stays a NavigationMenu.Link — the grid handles the layout rather than the link itself. This is the shape big SaaS marketing sites use to surface twelve to twenty links without a second scrollbar inside the panel.

The wider panel still anchors to left-0 top-full on its own item, so a wide menu under a left-side trigger may overflow the viewport on smaller laptops. Add lg:left-auto lg:right-0 on the Content of a right-side trigger to anchor the panel to the right edge of the item instead, which keeps the overflow inside the viewport. For very wide menus that span the full viewport width, render the menu inside a header container with relative positioning and override the Content className with inset-x-0 rather than left-0, which stretches the panel to the container edges. Pair this with the Card component if each link needs a thumbnail image or a richer body.

1import { NavigationMenu } from "@/components/ui/navigation-menu"
2import { Blocks, Palette, Terminal, BookOpen, Layers, Zap } from "lucide-react"
3
4export default function MegaMenu() {
5 return (
6 <NavigationMenu>
7 <NavigationMenu.List>
8 <NavigationMenu.Item>
9 <NavigationMenu.Trigger>Product</NavigationMenu.Trigger>
10 <NavigationMenu.Content className="min-w-[480px] grid grid-cols-2 gap-2">
11 <NavigationMenu.Link href="/components" className="items-center">
12 <Blocks />
13 <span className="font-medium">Components</span>
14 </NavigationMenu.Link>
15 <NavigationMenu.Link href="/theming" className="items-center">
16 <Palette />
17 <span className="font-medium">Theming</span>
18 </NavigationMenu.Link>
19 <NavigationMenu.Link href="/cli" className="items-center">
20 <Terminal />
21 <span className="font-medium">CLI</span>
22 </NavigationMenu.Link>
23 <NavigationMenu.Link href="/docs" className="items-center">
24 <BookOpen />
25 <span className="font-medium">Docs</span>
26 </NavigationMenu.Link>
27 <NavigationMenu.Link href="/templates" className="items-center">
28 <Layers />
29 <span className="font-medium">Templates</span>
30 </NavigationMenu.Link>
31 <NavigationMenu.Link href="/changelog" className="items-center">
32 <Zap />
33 <span className="font-medium">Changelog</span>
34 </NavigationMenu.Link>
35 </NavigationMenu.Content>
36 </NavigationMenu.Item>
37 </NavigationMenu.List>
38 </NavigationMenu>
39 )
40}

Responsive: NavigationMenu plus Drawer

On phones the dropdown pattern breaks down — there is no hover, the panel widths overflow the viewport, and the menu needs to sit vertically rather than horizontally. The standard pattern is to render the NavigationMenu only above the medium breakpoint and render a hamburger button plus a Drawer below it. Wrap the menu in <div className="hidden md:flex"> and the hamburger button in <div className="flex md:hidden">, so the two never appear at the same time and Tailwind's breakpoint utilities handle the swap with zero JavaScript.

The Drawer hosts the same route list as a vertical stack of links, ideally rendered from a shared navigation.ts constants array so the desktop menu and the mobile drawer never drift. Render each dropdown's panel as an Accordion item inside the drawer so the mobile user can expand sections rather than scrolling through every link in one long column. The pattern keeps a single source of truth for the information architecture while letting each breakpoint render the affordance that matches the input device. See Drivn vs shadcn NavigationMenu for the trade-offs between this CSS-driven pattern and the Radix-controlled equivalent.

1import { NavigationMenu } from "@/components/ui/navigation-menu"
2import { Drawer } from "@/components/ui/drawer"
3import { Button } from "@/components/ui/button"
4import { Menu } from "lucide-react"
5
6export default function ResponsiveHeader() {
7 return (
8 <header className="flex items-center justify-between px-6 py-4 border-b border-border">
9 <a href="/" className="font-bold">Drivn</a>
10 <div className="hidden md:flex">
11 <NavigationMenu>
12 <NavigationMenu.List>
13 <NavigationMenu.Item>
14 <NavigationMenu.Trigger>Components</NavigationMenu.Trigger>
15 <NavigationMenu.Content>
16 <NavigationMenu.Link href="/docs/components/button">
17 Button
18 </NavigationMenu.Link>
19 <NavigationMenu.Link href="/docs/components/card">
20 Card
21 </NavigationMenu.Link>
22 </NavigationMenu.Content>
23 </NavigationMenu.Item>
24 <NavigationMenu.Item>
25 <NavigationMenu.Link href="/docs">Docs</NavigationMenu.Link>
26 </NavigationMenu.Item>
27 </NavigationMenu.List>
28 </NavigationMenu>
29 </div>
30 <Drawer>
31 <Drawer.Trigger asChild>
32 <Button variant="outline" size="sm" className="md:hidden">
33 <Menu />
34 </Button>
35 </Drawer.Trigger>
36 <Drawer.Content>
37 <nav className="flex flex-col gap-2 p-4">
38 <a href="/docs/components/button">Button</a>
39 <a href="/docs/components/card">Card</a>
40 <a href="/docs">Docs</a>
41 </nav>
42 </Drawer.Content>
43 </Drawer>
44 </header>
45 )
46}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

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.