ComponentsNavigationMenu
NavigationMenu
Group page links in a menu with optional dropdown panels.
On this page
Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.
View registry files- React DOM
(react-dom)Portals for rendering overlay content outside the normal component tree.
Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.
Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.
Import the component into your page or component file.
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
NavigationMenuViewport,
} from "@dethink/components";NavigationMenu keeps links as links. Triggers are disclosure buttons with aria-expanded, and panels stay in the navigation's tab order.
Simple link bar
Current, disabled, and external states on a persistent top navigation.
Show sourceexamples/navigation-menu/basic.tsx
"use client";
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
} from "@dethink/components";
export function NavigationMenuBasic() {
return (
<NavigationMenu aria-label="Product">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink current href="#overview">
Overview
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#projects">Projects</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#reports">Reports</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink disabled href="#billing">
Billing
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink external href="https://status.example.com">
Status
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
);
}Examples that combine components for common tasks.
Brand navbar
A full marketing navbar: brand logo, icon-led menu with rich flyouts and the animated indicator, plus a theme toggle, login, and call-to-action cluster. When the bar gets narrow, the burger expands an integrated panel with accordion submenus — no overlay.
Product navigation
Rich flyout panels with a featured card, grouped links, icons, descriptions, and the animated indicator.
App topbar with mobile handoff
A compact quiet nav on desktop; on small screens the same links hand off to a Dialog. Resize the viewport to see the collapse.
Show sourceexamples/navigation-menu/app-topbar.tsx
"use client";
import {
Dialog,
DialogContent,
DialogTitle,
DialogTrigger,
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
} from "@dethink/components";
import { Menu } from "lucide-react";
const sections = [
{ href: "#dashboard", label: "Dashboard", current: true },
{ href: "#deployments", label: "Deployments", current: false },
{ href: "#monitoring", label: "Monitoring", current: false },
{ href: "#team", label: "Team", current: false },
];
export function NavigationMenuAppTopbar() {
return (
<header className="border-border bg-muted/30 flex items-center justify-between gap-4 rounded-md border px-4 py-2">
<span className="text-foreground text-sm font-semibold">Acme Cloud</span>
{/* Desktop: persistent quiet nav. */}
<NavigationMenu
aria-label="Dashboard"
variant="quiet"
size="sm"
className="max-md:hidden"
>
<NavigationMenuList>
{sections.map((section) => (
<NavigationMenuItem key={section.href}>
<NavigationMenuLink current={section.current} href={section.href}>
{section.label}
</NavigationMenuLink>
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenu>
{/* Mobile: the same links hand off to a Dialog. NavigationMenu stays a
navigation primitive — the Dialog owns the overlay behavior. */}
<div className="md:hidden">
<Dialog>
<DialogTrigger
aria-label="Open navigation"
size="icon"
variant="outline"
>
<Menu aria-hidden="true" className="size-4" />
</DialogTrigger>
<DialogContent>
<DialogTitle>Navigate</DialogTitle>
<div className="px-[var(--dt-space-6)] pb-[var(--dt-space-6)]">
<NavigationMenu
aria-label="Dashboard"
orientation="vertical"
variant="quiet"
>
<NavigationMenuList>
{sections.map((section) => (
<NavigationMenuItem key={section.href}>
<NavigationMenuLink
current={section.current}
href={section.href}
>
{section.label}
</NavigationMenuLink>
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenu>
</div>
</DialogContent>
</Dialog>
</div>
<span className="text-muted-foreground text-sm max-md:hidden">
workspace: prod
</span>
</header>
);
}Mobile navigation drawer
A phone-sized in-page drawer: a disclosure button toggles the panel, and the vertical menu restyles its flyout into an accordion — links, icons, current state, and auth actions stay intact.
Documentation hub
Underline variant with grouped guide flyouts and aria-current=location for the active page.
Show sourceexamples/navigation-menu/docs-hub.tsx
"use client";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuDescription,
NavigationMenuItem,
NavigationMenuLabel,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuSection,
NavigationMenuSeparator,
NavigationMenuTrigger,
} from "@dethink/components";
export function NavigationMenuDocsHub() {
return (
<div className="min-h-[20rem]">
<NavigationMenu aria-label="Documentation" variant="underline">
<NavigationMenuList>
<NavigationMenuItem value="guides">
<NavigationMenuTrigger>Guides</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuSection>
<NavigationMenuLabel>Getting started</NavigationMenuLabel>
<NavigationMenuLink current="location" href="#install">
Installation
<NavigationMenuDescription>
Registry setup and base tokens.
</NavigationMenuDescription>
</NavigationMenuLink>
<NavigationMenuLink href="#theming">
Theming
<NavigationMenuDescription>
Light, dark, density, and brand palettes.
</NavigationMenuDescription>
</NavigationMenuLink>
</NavigationMenuSection>
<NavigationMenuSeparator orientation="vertical" />
<NavigationMenuSection>
<NavigationMenuLabel>Recipes</NavigationMenuLabel>
<NavigationMenuLink href="#forms">Forms</NavigationMenuLink>
<NavigationMenuLink href="#tables">Tables</NavigationMenuLink>
<NavigationMenuLink href="#navigation">
Navigation
</NavigationMenuLink>
</NavigationMenuSection>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="reference">
<NavigationMenuTrigger>Reference</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuSection>
<NavigationMenuLink href="#api">
Component API
</NavigationMenuLink>
<NavigationMenuLink href="#tokens">Tokens</NavigationMenuLink>
</NavigationMenuSection>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#changelog">Changelog</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
);
}Dashboard composition
A quiet topbar plus a vertical section nav composing an app shell — without absorbing Sidebar's job.
Show sourceexamples/navigation-menu/dashboard.tsx
"use client";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
} from "@dethink/components";
export function NavigationMenuDashboard() {
return (
<div className="space-y-4">
<header className="border-border bg-muted/30 flex items-center justify-between gap-4 rounded-md border px-4 py-2">
<span className="text-foreground text-sm font-semibold">Insights</span>
<NavigationMenu aria-label="Workspace areas" size="sm" variant="quiet">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink href="#home">Home</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink current href="#reports">
Reports
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#alerts">Alerts</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</header>
<div className="grid gap-4 md:grid-cols-[12rem_minmax(0,1fr)]">
<NavigationMenu
aria-label="Report sections"
orientation="vertical"
size="sm"
variant="quiet"
>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink current="location" href="#revenue">
Revenue
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#retention">
Retention
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#usage">Usage</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<Card>
<CardHeader>
<CardTitle>Revenue report</CardTitle>
<CardDescription>Updated from pipeline run #428.</CardDescription>
</CardHeader>
<CardContent>
<p className="text-muted-foreground text-sm">
Two NavigationMenu instances compose the shell: a quiet topbar for
workspace areas and a vertical section list for the active report.
A full application shell with a collapsible sidebar stays out of
scope for NavigationMenu — that is the future Sidebar component.
</p>
</CardContent>
</Card>
</div>
</div>
);
}Overflow collapse
Secondary destinations collapse behind a More disclosure on narrow screens and render inline when space allows.
Show sourceexamples/navigation-menu/overflow.tsx
"use client";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuSection,
NavigationMenuTrigger,
} from "@dethink/components";
export function NavigationMenuOverflow() {
return (
<div className="min-h-[16rem]">
<NavigationMenu aria-label="Product">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink current href="#overview">
Overview
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#projects">Projects</NavigationMenuLink>
</NavigationMenuItem>
{/* Secondary destinations stay reachable on narrow screens by
collapsing behind a disclosure trigger instead of wrapping. */}
<NavigationMenuItem className="lg:hidden" value="more">
<NavigationMenuTrigger>More</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuSection>
<NavigationMenuLink href="#reports">Reports</NavigationMenuLink>
<NavigationMenuLink href="#audit">Audit log</NavigationMenuLink>
<NavigationMenuLink href="#settings">
Settings
</NavigationMenuLink>
</NavigationMenuSection>
</NavigationMenuContent>
</NavigationMenuItem>
{/* On wide screens the same links render inline. */}
<NavigationMenuItem className="max-lg:hidden">
<NavigationMenuLink href="#reports">Reports</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem className="max-lg:hidden">
<NavigationMenuLink href="#audit">Audit log</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem className="max-lg:hidden">
<NavigationMenuLink href="#settings">Settings</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
);
}| Prop | What it does | Default |
|---|---|---|
variant / size / orientation"default" | "quiet" | "underline" / "sm" | "md" | "lg" / "horizontal" | "vertical" | Token-backed presentation of the whole menu; links and triggers inherit these through context. | "default" / "md" / "horizontal" |
value / defaultValue / onValueChangestring | null / string | null / (value) => void | Controlled or uncontrolled open flyout item, keyed by NavigationMenuItem value. | Not set |
activationMode"click" | "hover" | "focus" | "manual" | How triggers open panels. Hover adds an intent delay and keeps click/keyboard activation working; manual leaves all state changes to the consumer. | "click" |
delay / closeDelaynumber / number | Hover-intent timings in milliseconds for hover activation. | 150 / 300 |
motion"none" | "subtle" | "standard" | "expressive" | CSS motion preset for panel transitions, directional slides, and staggered reveal. All movement respects prefers-reduced-motion. | "standard" |
NavigationMenuLink — currentboolean | "page" | "location" | Marks the current destination with aria-current and a stable data-current attribute. | Not set |
NavigationMenuLink — disabled / external / icon / asChildboolean / boolean / ReactNode / boolean | Disabled links drop their href and expose aria-disabled; external links get safe rel handling; asChild composes router links. | Not set |
NavigationMenuItem — valuestring | Stable identity for controlled state, indicator mapping, and directional transitions. | auto |
NavigationMenuTriggerbutton (disclosure semantics) | Native button exposing aria-expanded/aria-controls; never role=menu. showChevron hides the built-in caret. | Not set |
NavigationMenuViewport / NavigationMenuIndicatoroptional anatomy | Shared morphing panel host and animated active-item marker; both are optional layers over the inline flyout behavior. | Not set |