suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
GitHubGet started
DocsComponentsPatternsInteractionsChartsBlocks
Components82
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Wheel Picker
  • Widget

suiss/ui · Component

Navigation Menu

A site navigation bar whose items open popover or full-width panels of links.

Navigation Menu is an open-source component from suiss/ui for React. The @esuiss/ui CLI copies its source into your project, so you own the code.

Navigation Menu wraps the navigation menu primitive of the chosen base. NavigationMenu is the root; inside it, NavigationMenuList holds NavigationMenuItem entries. An item contains either a NavigationMenuTrigger with NavigationMenuContent, or a single NavigationMenuLink. Inside content, use NavigationMenuLabel for column headings and NavigationMenuLink for destinations.

The root renders the popup for you. With layout="popover" it adds a NavigationMenuPositioner, a floating card that resizes and slides between items. With layout="panel" it adds a NavigationMenuPanel, a full-width sheet under the bar with a blurred backdrop, like a store's global navigation. Use Navigation Menu for site navigation links; use Menubar or Dropdown Menu for commands.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/navigation-menu

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add navigation-menu

Manual

  1. Install the dependencies and add the cn helper:
$ pnpm add @base-ui/react @phosphor-icons/react class-variance-authority
  1. Copy the source into components/ui/navigation-menu.tsx:
  1. Update the import paths to match your project.

Usage

import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuPositioner, NavigationMenuPanel, NavigationMenuLink, NavigationMenuLabel, NavigationMenuIndicator } 
<div












































Give each item with content a value so the menu can be opened by default or controlled with value, defaultValue and onValueChange on the root. The root also accepts align for the popover and anchor for either layout; in panel layout, pass a ref to the full-width bar as anchor so the panel spans its width.

To style a top-level link like a trigger, pass navigationMenuTriggerStyle() as its className. Set active on a link to mark the current page.

Examples

By default

A menu that opens the Products item on mount with defaultValue.

Links only

A bar of plain links styled like triggers, with the first marked active.

Panel

The panel layout anchored to a sticky header, with columns of labelled links and large primary links.

API Reference

Anatomy7 parts
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger />
<NavigationMenuContent>
<NavigationMenuLink />
</NavigationMenuContent
01

NavigationMenu

The root that manages open state and renders the popover or panel popup.

PropTypeDefaultDescription
layout"popover" | "panel""popover"Chooses the popup style: popover for a floating card, or panel for a full-width sheet with a backdrop.

Also accepts the props of the primitive parts it wraps.

02

NavigationMenuList

The horizontal list of top-level items.

Also accepts the props of React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.List>.

03

NavigationMenuItem

One top-level entry, holding a trigger with content or a single link.

Also accepts the props of React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Item>.

04

NavigationMenuTrigger

The button that opens its item's content, with a caret in the popover layout.

Also accepts the props of the primitive part it wraps.

05

NavigationMenuContent

The content shown in the popup when its item is open.

Also accepts the props of the primitive part it wraps.

06

NavigationMenuPositioner

The floating popover card used by the popover layout.

Also accepts the props of the primitive part it wraps.

07

NavigationMenuPanel

The full-width sheet and backdrop used by the panel layout.

Also accepts the props of the primitive part it wraps.

08

NavigationMenuLink

A navigation link, either in the bar or inside content.

PropTypeDefaultDescription
size"default" | "lg""default"Sets the link size; lg uses large, semibold text for primary destinations.

Also accepts the props of the primitive part it wraps.

09

NavigationMenuLabel

A small secondary heading above a group of links.

Also accepts the props of React.ComponentProps<"p">.

10

NavigationMenuIndicator

A small arrow that fades in while its menu is open.

Also accepts the props of React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Icon>.

Accessibility

  • Triggers are buttons with aria-expanded reflecting whether their content is open.
  • Enter opens a focused trigger, and Tab moves focus into the open content.
  • Escape closes the open content and returns focus to its trigger.
  • Hovering another trigger switches the open content.
  • A link with active set receives aria-current="page".
  • The trigger's caret icon is aria-hidden.

Notes

  • Styling hooks: data-slot values such as navigation-menu, navigation-menu-trigger, navigation-menu-content, navigation-menu-popup, navigation-menu-panel, navigation-menu-backdrop and navigation-menu-link. The root exposes data-layout; links expose data-size and data-active.
  • Triggers and links restyle themselves automatically in the panel layout, and panel list items animate in with a stagger that respects reduced motion.
  • navigationMenuTriggerStyle is exported for styling links as triggers.
  • Uses the popover, background, accent and primary colors, with a blurred bg-black/20 backdrop (backdrop-blur-lg).
NextPage IndicatorPreviousNative Select

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • By default
    • Links only
    • Panel
  4. 04API Reference
    • NavigationMenu
    • NavigationMenuList
    • NavigationMenuItem
    • NavigationMenuTrigger
    • NavigationMenuContent
    • NavigationMenuPositioner
    • NavigationMenuPanel
    • NavigationMenuLink
    • NavigationMenuLabel
    • NavigationMenuIndicator
  5. 05Accessibility
  6. 06Notes
from
"@/components/ui/navigation-menu"
className
=
"flex min-h-96 items-start justify-center"
>
<NavigationMenu align="start">
<NavigationMenuList>
<NavigationMenuItem value="products">
<NavigationMenuTrigger>Products</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-96 gap-1">
{products.map((product) => (
<li key={product.title}>
<NavigationMenuLink href="#" className="items-start gap-3">
<product.icon className="mt-0.5" />
<div className="flex flex-col gap-1">
<span className="font-medium">{product.title}</span>
<span className="text-muted-foreground">
{product.description}
</span>
</div>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="resources">
<NavigationMenuTrigger>Resources</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-56 gap-1">
{resources.map((resource) => (
<li key={resource.title}>
<NavigationMenuLink href="#">
<resource.icon />
{resource.title}
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#" className={navigationMenuTriggerStyle()}>
Pricing
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
>
<NavigationMenuLink />
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
import {
  BookOpenIcon,
  ChartLineIcon,
  CodeIcon,
  LifebuoyIcon,
  LightningIcon,
  ShieldCheckIcon,
} from "@phosphor-icons/react"
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"

const products = [
  {
    title: "Analytics",
    description: "Track traffic and conversions in real time.",
    icon: (
      <ChartLineIcon className="mt-0.5" />
    ),
  },
  {
    title: "Automations",
    description: "Run workflows when events happen.",
    icon: (
      <LightningIcon className="mt-0.5" />
    ),
  },
  {
    title: "Security",
    description: "Single sign-on, audit logs, and roles.",
    icon: (
      <ShieldCheckIcon className="mt-0.5" />
    ),
  },
]

const resources = [
  {
    title: "Documentation",
    icon: (
      <BookOpenIcon />
    ),
  },
  {
    title: "API reference",
    icon: (
      <CodeIcon />
    ),
  },
  {
    title: "Help center",
    icon: (
      <LifebuoyIcon />
    ),
  },
]

export default function NavigationMenuDemo() {
  return (
    <div className="flex min-h-96 items-start justify-center">
      <NavigationMenu align="start">
        <NavigationMenuList>
          <NavigationMenuItem value="products">
            <NavigationMenuTrigger>Products</NavigationMenuTrigger>
            <NavigationMenuContent>
              <ul className="grid w-96 gap-1">
                {products.map((product) => (
                  <li key={product.title}>
                    <NavigationMenuLink href="#" className="items-start gap-3">
                      {product.icon}
                      <div className="flex flex-col gap-1">
                        <span className="font-medium">{product.title}</span>
                        <span className="text-muted-foreground">
                          {product.description}
                        </span>
                      </div>
                    </NavigationMenuLink>
                  </li>
                ))}
              </ul>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem value="resources">
            <NavigationMenuTrigger>Resources</NavigationMenuTrigger>
            <NavigationMenuContent>
              <ul className="grid w-56 gap-1">
                {resources.map((resource) => (
                  <li key={resource.title}>
                    <NavigationMenuLink href="#">
                      {resource.icon}
                      {resource.title}
                    </NavigationMenuLink>
                  </li>
                ))}
              </ul>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuLink
              href="#"
              className={navigationMenuTriggerStyle()}
            >
              Pricing
            </NavigationMenuLink>
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </div>
  )
}
import {
  BookOpenIcon,
  ChartLineIcon,
  CodeIcon,
  LifebuoyIcon,
  LightningIcon,
  ShieldCheckIcon,
} from "@phosphor-icons/react"
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"

const products = [
  {
    title: "Analytics",
    description: "Track traffic and conversions in real time.",
    icon: (
      <ChartLineIcon className="mt-0.5" />
    ),
  },
  {
    title: "Automations",
    description: "Run workflows when events happen.",
    icon: (
      <LightningIcon className="mt-0.5" />
    ),
  },
  {
    title: "Security",
    description: "Single sign-on, audit logs, and roles.",
    icon: (
      <ShieldCheckIcon className="mt-0.5" />
    ),
  },
]

const resources = [
  {
    title: "Documentation",
    icon: (
      <BookOpenIcon />
    ),
  },
  {
    title: "API reference",
    icon: (
      <CodeIcon />
    ),
  },
  {
    title: "Help center",
    icon: (
      <LifebuoyIcon />
    ),
  },
]

export default function NavigationMenuOpenByDefault() {
  return (
    <div className="flex min-h-96 items-start justify-center">
      <NavigationMenu align="start" defaultValue="products">
        <NavigationMenuList>
          <NavigationMenuItem value="products">
            <NavigationMenuTrigger>Products</NavigationMenuTrigger>
            <NavigationMenuContent>
              <ul className="grid w-96 gap-1">
                {products.map((product) => (
                  <li key={product.title}>
                    <NavigationMenuLink href="#" className="items-start gap-3">
                      {product.icon}
                      <div className="flex flex-col gap-1">
                        <span className="font-medium">{product.title}</span>
                        <span className="text-muted-foreground">
                          {product.description}
                        </span>
                      </div>
                    </NavigationMenuLink>
                  </li>
                ))}
              </ul>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem value="resources">
            <NavigationMenuTrigger>Resources</NavigationMenuTrigger>
            <NavigationMenuContent>
              <ul className="grid w-56 gap-1">
                {resources.map((resource) => (
                  <li key={resource.title}>
                    <NavigationMenuLink href="#">
                      {resource.icon}
                      {resource.title}
                    </NavigationMenuLink>
                  </li>
                ))}
              </ul>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuLink
              href="#"
              className={navigationMenuTriggerStyle()}
            >
              Pricing
            </NavigationMenuLink>
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </div>
  )
}
import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"

export default function NavigationMenuLinksOnly() {
  return (
    <div className="flex min-h-96 items-start justify-center">
      <NavigationMenu align="start">
        <NavigationMenuList>
          {["Overview", "Customers", "Changelog", "Blog"].map(
            (label, index) => (
              <NavigationMenuItem key={label}>
                <NavigationMenuLink
                  href="#"
                  active={index === 0}
                  className={navigationMenuTriggerStyle()}
                >
                  {label}
                </NavigationMenuLink>
              </NavigationMenuItem>
            )
          )}
        </NavigationMenuList>
      </NavigationMenu>
    </div>
  )
}
components/ui/navigation-menu.tsx
"use client"

import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"
import { CaretDownIcon } from "@phosphor-icons/react"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"

function NavigationMenu({
  align = "start",
  layout = "popover",
  anchor,
  className,
  children,
  ...props
}: NavigationMenuPrimitive.Root.Props &
  Pick<NavigationMenuPrimitive.Positioner.Props, "align" | "anchor"> & {
import * as React from "react"
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLabel,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"

const panelMenus = [
  {
    value: "pos",
    label: "POS",
    explore: ["Terminals", "Handhelds", "Compare models"],
    shop: ["Shop POS", "Accessories", "Financing", "Trade in"],
    more: ["POS support", "Setup guide", "Integrations"],
  },
  {
    value: "kitchen",
    label: "Kitchen",
    explore: ["Displays", "Printers"],
    shop: ["Shop Kitchen", "Mounts", "Cables"],
    more: ["Kitchen support", "Station layouts"],
  },
  {
    value: "reservations",
    label: "Reservations",
    explore: ["Floor plans", "Waitlist", "Guest profiles", "Online booking"],
    shop: ["Plans", "Add-ons"],
    more: ["Reservations support", "Booking widget", "Calendar sync"],
  },
  {
    value: "analytics",
    label: "Analytics",
    explore: ["Reports", "Forecasts"],
    shop: ["Plans", "Data exports"],
    more: ["Analytics support"],
  },
]

function PanelColumn({
  label,
  links,
  size,
}: {
  label: string
  links: string[]
  size?: "default" | "lg"
}) {
  return (
    <div className="flex flex-col">
      <NavigationMenuLabel>{label}</NavigationMenuLabel>
      <ul className="flex flex-col gap-1.5">
        {links.map((link) => (
          <li key={link}>
            <NavigationMenuLink href="#" size={size}>
              {link}
            </NavigationMenuLink>
          </li>
        ))}
      </ul>
    </div>
  )
}

function PanelExample() {
  const ref = React.useRef<HTMLDivElement>(null)

  return (
    <div className="w-full">
      <div
        ref={ref}
        className="sticky top-0 z-50 flex w-full justify-center bg-background/80 backdrop-blur-xl transition-colors duration-300 has-data-popup-open:bg-background dark:bg-muted/80 dark:has-data-popup-open:bg-muted"
      >
        <NavigationMenu layout="panel" anchor={ref}>
          <NavigationMenuList>
            <NavigationMenuItem>
              <NavigationMenuLink
                href="#"
                className={navigationMenuTriggerStyle()}
              >
                Store
              </NavigationMenuLink>
            </NavigationMenuItem>
            {panelMenus.map((menu) => (
              <NavigationMenuItem key={menu.value} value={menu.value}>
                <NavigationMenuTrigger>{menu.label}</NavigationMenuTrigger>
                <NavigationMenuContent>
                  <div className="mx-auto flex max-w-245 gap-16 px-6 pt-10 pb-16">
                    <PanelColumn
                      label={`Explore ${menu.label}`}
                      links={menu.explore}
                      size="lg"
                    />
                    <PanelColumn
                      label={`Shop ${menu.label}`}
                      links={menu.shop}
                    />
                    <PanelColumn
                      label={`More from ${menu.label}`}
                      links={menu.more}
                    />
                  </div>
                </NavigationMenuContent>
              </NavigationMenuItem>
            ))}
            <NavigationMenuItem>
              <NavigationMenuLink
                href="#"
                className={navigationMenuTriggerStyle()}
              >
                Support
              </NavigationMenuLink>
            </NavigationMenuItem>
          </NavigationMenuList>
        </NavigationMenu>
      </div>
      <div className="mx-auto flex max-w-245 flex-col items-center gap-3 px-6 py-24 text-center">
        <h2 className="text-5xl font-semibold">Hestia POS</h2>
        <p className="text-xl text-muted-foreground">
          Orders, kitchen and tables in one place.
        </p>
        <div className="mt-8 grid w-full grid-cols-3 gap-4">
          {["bg-blue-500", "bg-orange-500", "bg-green-500"].map((tint) => (
            <div key={tint} className={`aspect-4/3 rounded-3xl ${tint}`} />
          ))}
        </div>
      </div>
    </div>
  )
}

export default function NavigationMenuPanel() {
  return (
    <div className="flex min-h-96 items-start justify-center">
      <PanelExample />
    </div>
  )
}