RadCN
Navigationreadycomponentready

Navigation Menu

A dependency-free navigation menu with trigger-owned panels, link triggers, viewport sizing, indicator hooks, and keyboard navigation.

Importimport { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger } from 'radcn/navigation-menu'
Preview

Live package example

Render the upstream docs-style navigation menu with exact panel copy, responsive sections, icon links, and viewport behavior.

Navigation Menu Demo

Render the upstream docs-style navigation menu with exact panel copy, responsive sections, icon links, and viewport behavior.

Preview
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from 'radcn/navigation-menu'

const components = [
  {
    title: 'Alert Dialog',
    href: '/docs/primitives/alert-dialog',
    description:
      'A modal dialog that interrupts the user with important content and expects a response.',
  },
  {
    title: 'Hover Card',
    href: '/docs/primitives/hover-card',
    description: 'For sighted users to preview content available behind a link.',
  },
  {
    title: 'Progress',
    href: '/docs/primitives/progress',
    description:
      'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
  },
  {
    title: 'Scroll-area',
    href: '/docs/primitives/scroll-area',
    description: 'Visually or semantically separates content.',
  },
  {
    title: 'Tabs',
    href: '/docs/primitives/tabs',
    description:
      'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
  },
  {
    title: 'Tooltip',
    href: '/docs/primitives/tooltip',
    description:
      'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
  },
]

export function NavigationMenuDemo() {
  return (
    <NavigationMenu defaultValue="home" id="navigation-menu-demo">
      <NavigationMenuList class="flex-wrap">
        <NavigationMenuItem value="home">
          <NavigationMenuTrigger>Home</NavigationMenuTrigger>
          <NavigationMenuContent>
            <div class="navigation-menu-home-grid">
              <NavigationMenuLink href="/">
                <div>shadcn/ui</div>
                <p>Beautifully designed components built with Tailwind CSS.</p>
              </NavigationMenuLink>
              <div>
                <ListItem href="/docs" title="Introduction">
                  Re-usable components built using Radix UI and Tailwind CSS.
                </ListItem>
                <ListItem href="/docs/installation" title="Installation">
                  How to install dependencies and structure your app.
                </ListItem>
                <ListItem href="/docs/primitives/typography" title="Typography">
                  Styles for headings, paragraphs, lists...etc
                </ListItem>
              </div>
            </div>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem value="components">
          <NavigationMenuTrigger>Components</NavigationMenuTrigger>
          <NavigationMenuContent>
            <div class="navigation-menu-components-grid">
              {components.map((component) => (
                <ListItem href={component.href} title={component.title}>
                  {component.description}
                </ListItem>
              ))}
            </div>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem value="docs">
          <NavigationMenuLink href="/docs">Docs</NavigationMenuLink>
        </NavigationMenuItem>
        <NavigationMenuItem class="desktop-only" value="list">
          <NavigationMenuTrigger>List</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ListItem href="#" title="Components">Browse all components in the library.</ListItem>
            <ListItem href="#" title="Documentation">Learn how to use the library.</ListItem>
            <ListItem href="#" title="Blog">Read our latest blog posts.</ListItem>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem class="desktop-only" value="simple">
          <NavigationMenuTrigger>Simple</NavigationMenuTrigger>
          <NavigationMenuContent>
            <NavigationMenuLink href="#">Components</NavigationMenuLink>
            <NavigationMenuLink href="#">Documentation</NavigationMenuLink>
            <NavigationMenuLink href="#">Blocks</NavigationMenuLink>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem class="desktop-only" value="with-icon">
          <NavigationMenuTrigger>With Icon</NavigationMenuTrigger>
          <NavigationMenuContent>
            <NavigationMenuLink href="#">? Backlog</NavigationMenuLink>
            <NavigationMenuLink href="#">o To Do</NavigationMenuLink>
            <NavigationMenuLink href="#">✓ Done</NavigationMenuLink>
          </NavigationMenuContent>
        </NavigationMenuItem>
      </NavigationMenuList>
      <NavigationMenuIndicator />
      <NavigationMenuViewport />
    </NavigationMenu>
  )
}

function ListItem({ children, href, title }) {
  return (
    <NavigationMenuLink href={href}>
      <div>{title}</div>
      <p>{children}</p>
    </NavigationMenuLink>
  )
}

Installation

Intended future install command. RadCN is private and not published to npm yet, so this snippet documents the target user-facing API rather than something external consumers can run today.

pnpm add radcn # intended future package
import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger } from 'radcn/navigation-menu'

Theming

RadCN tokens read the resolved theme from the document. Store the user's preference separately, then resolve system preferences to a concrete light or dark theme before setting package tokens.

<html data-radcn-theme-mode="system" data-radcn-theme="dark">
  ...
</html>

Accessibility

  • NavigationMenu renders a labelled nav landmark and native list structure for top-level controls.
  • Triggers receive aria-controls and aria-expanded during enhanceNavigationMenu, while content panels stay associated with their owning item.
  • Links remain native anchors, so the Docs trigger-style link keeps normal link behavior.
  • Keyboard movement covers horizontal roving focus, Home, End, Enter or Space opening, Escape close, and focusout close without React state.

Customization

  • Root, list, item, trigger, content, link, viewport, and indicator parts expose public data-radcn-navigation-menu hooks and package classes.
  • The Docs link uses the public NavigationMenuLink class as the RadCN equivalent of upstream navigationMenuTriggerStyle().
  • The upstream viewport={isMobile} recipe maps to explicit NavigationMenuViewport composition plus documented app-owned responsive behavior.
  • Responsive desktop-only sections use app-owned CSS over public item classes, preserving the upstream hidden md:block behavior without Tailwind.
  • Icon links are app-owned presentation over NavigationMenuLink; apps may use text glyphs, inline SVG, or an icon package without changing RadCN.

Remix 3 Notes

  • use client, React state, Next Link, useIsMobile, Radix Navigation Menu, and cva map to server-rendered RadCN markup plus scoped enhanceNavigationMenu browser behavior.
  • lucide ChevronDownIcon, CircleHelpIcon, CircleIcon, and CircleCheckIcon remain app presentation and are not RadCN dependencies.
  • className maps to class, cn maps to explicit class composition, and data-slot maps to public data-radcn-navigation-menu* hooks.
  • Tailwind grid, width, gap, flex-wrap, rounded, muted, transition, hidden md:block, and text utilities map to package CSS, docs-owned styles, CSS variables, and media queries.
  • NavigationMenuIndicator is package capability evidence; the upstream demo relies on root-rendered viewport behavior rather than explicitly rendering an indicator.
  • Vendor source remains read-only evidence and is not imported by RadCN.