HextaUI

Navigation menu

Seitennavigation mit Dropdowns oder einem Panel in voller Breite, das offen bleibt, während der Inhalt zwischen Menüs gleitet.

import {
  IconBook,
  IconBrandGithub,
  IconBuildingStore,
  IconChartBar,
  IconCode,
  IconComponents,
  IconLayoutDashboard,
  IconLifebuoy,
  IconPalette,
  IconRocket,
  IconShieldLock,
  IconUsers,
} from "@tabler/icons-react"

import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"

type Entry = {
  title: string
  description: string
  icon: typeof IconBook
}

const products: Entry[] = [
  {
    title: "Components",
    description: "Accessible building blocks with motion built in.",
    icon: IconComponents,
  },
  {
    title: "Blocks",
    description: "Full sections you can drop into a page.",
    icon: IconLayoutDashboard,
  },
  {
    title: "Themes",
    description: "Tokens for color, radius and type.",
    icon: IconPalette,
  },
  {
    title: "Templates",
    description: "Starter apps ready to deploy.",
    icon: IconBuildingStore,
  },
]

const solutions: Entry[] = [
  {
    title: "Startups",
    description: "Ship a polished product on day one.",
    icon: IconRocket,
  },
  {
    title: "Teams",
    description: "One system shared across every app.",
    icon: IconUsers,
  },
  {
    title: "Analytics",
    description: "Dashboards, tables and charts.",
    icon: IconChartBar,
  },
  {
    title: "Security",
    description: "Audited for accessibility and safe defaults.",
    icon: IconShieldLock,
  },
  {
    title: "Developers",
    description: "Typed APIs that read like shadcn.",
    icon: IconCode,
  },
  {
    title: "Open source",
    description: "Read every line on GitHub.",
    icon: IconBrandGithub,
  },
]

const resources: Entry[] = [
  {
    title: "Documentation",
    description: "Every component, example and prop.",
    icon: IconBook,
  },
  {
    title: "Support",
    description: "Questions, bugs and feature requests.",
    icon: IconLifebuoy,
  },
]

function Links({ entries }: { entries: Entry[] }) {
  return (
    <ul className="grid gap-1 sm:grid-cols-2">
      {entries.map((entry) => (
        <li key={entry.title}>
          <NavigationMenuLink href="#">
            <span className="flex size-9 shrink-0 items-center justify-center rounded-md bg-background ring-(length:--hairline) ring-border">
              <entry.icon className="text-foreground" />
            </span>
            <span className="flex min-w-0 flex-col">
              <span className="font-medium">{entry.title}</span>
              <span className="text-muted-foreground">{entry.description}</span>
            </span>
          </NavigationMenuLink>
        </li>
      ))}
    </ul>
  )
}

export function NavigationMenuDemo() {
  return (
    <div className="w-full max-w-2xl rounded-xl border px-2 py-1.5">
      <NavigationMenu variant="panel" aria-label="Main">
        <NavigationMenuList>
          <NavigationMenuItem>
            <NavigationMenuTrigger>Products</NavigationMenuTrigger>
            <NavigationMenuContent>
              <Links entries={products} />
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuTrigger>Solutions</NavigationMenuTrigger>
            <NavigationMenuContent>
              <Links entries={solutions} />
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuTrigger>Resources</NavigationMenuTrigger>
            <NavigationMenuContent>
              <Links entries={resources} />
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuLink href="#">Pricing</NavigationMenuLink>
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/navigation-menu.json

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
<NavigationMenu variant="panel" aria-label="Main">
  <NavigationMenuList>
    <NavigationMenuItem>
      <NavigationMenuTrigger>Products</NavigationMenuTrigger>
      <NavigationMenuContent>
        <NavigationMenuLink href="/components">Components</NavigationMenuLink>
      </NavigationMenuContent>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuLink href="/pricing">Pricing</NavigationMenuLink>
    </NavigationMenuItem>
  </NavigationMenuList>
</NavigationMenu>

Mit variant="panel" überspannt eine Karte die ganze Leiste. Beim Wechsel zwischen Triggern bleibt sie offen und an Ort und Stelle: Nur der Inhalt gleitet in die Richtung, in die du dich bewegt hast, und die Höhe passt sich sanft an. Nichts schließt, öffnet neu oder springt.

NavigationMenu
└── NavigationMenuList
    ├── NavigationMenuItem
    │   ├── NavigationMenuTrigger
    │   └── NavigationMenuContent
    │       └── NavigationMenuLink
    └── NavigationMenuItem
        └── NavigationMenuLink

Die Standardvariante dropdown öffnet unter jedem Trigger eine Karte. Beim Wechsel zum nächsten Trigger gleitet dieselbe Karte hinüber und passt ihre Größe dem neuen Inhalt an.

import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"

const guides = [
  { title: "Installation", description: "Add HextaUI to a Next.js app." },
  { title: "Theming", description: "Colors, radius and dark mode." },
  { title: "Motion", description: "Durations, easing and reduced motion." },
]

const components = [
  "Accordion",
  "Button",
  "Dialog",
  "Menubar",
  "Table",
  "Tooltip",
]

export function NavigationMenuDropdown() {
  return (
    <NavigationMenu aria-label="Docs">
      <NavigationMenuList>
        <NavigationMenuItem>
          <NavigationMenuTrigger>Guides</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ul className="flex w-72 flex-col gap-1">
              {guides.map((guide) => (
                <li key={guide.title}>
                  <NavigationMenuLink href="#">
                    <span className="flex flex-col">
                      <span className="font-medium">{guide.title}</span>
                      <span className="text-muted-foreground">
                        {guide.description}
                      </span>
                    </span>
                  </NavigationMenuLink>
                </li>
              ))}
            </ul>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuTrigger>Components</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ul className="grid w-80 grid-cols-2 gap-1">
              {components.map((name) => (
                <li key={name}>
                  <NavigationMenuLink href="#">{name}</NavigationMenuLink>
                </li>
              ))}
            </ul>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="#">Blog</NavigationMenuLink>
        </NavigationMenuItem>
      </NavigationMenuList>
    </NavigationMenu>
  )
}

Aktuelle Seite

Links in der Leiste sehen aus wie Trigger. Markiere die aktuelle Seite mit active, das auch aria-current="page" setzt.

import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
} from "@/components/ui/navigation-menu"

const links = ["Overview", "Activity", "Settings"]

export function NavigationMenuActive() {
  return (
    <NavigationMenu aria-label="Project">
      <NavigationMenuList>
        {links.map((link) => (
          <NavigationMenuItem key={link}>
            <NavigationMenuLink href="#" active={link === "Activity"}>
              {link}
            </NavigationMenuLink>
          </NavigationMenuItem>
        ))}
      </NavigationMenuList>
    </NavigationMenu>
  )
}

Rechts nach links

Inhalte gleiten in die Richtung, in die du dich bewegst, die sich in rechts-nach-links-Layouts umkehrt.

import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"

const menus = [
  { label: "المنتجات", links: ["المكونات", "القوالب", "السمات"] },
  { label: "الموارد", links: ["التوثيق", "الدعم"] },
]

export function NavigationMenuRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md rounded-xl border px-2 py-1.5">
      <NavigationMenu variant="panel" aria-label="الرئيسية">
        <NavigationMenuList>
          {menus.map((menu) => (
            <NavigationMenuItem key={menu.label}>
              <NavigationMenuTrigger>{menu.label}</NavigationMenuTrigger>
              <NavigationMenuContent>
                <ul className="grid grid-cols-2 gap-1">
                  {menu.links.map((link) => (
                    <li key={link}>
                      <NavigationMenuLink href="#">{link}</NavigationMenuLink>
                    </li>
                  ))}
                </ul>
              </NavigationMenuContent>
            </NavigationMenuItem>
          ))}
        </NavigationMenuList>
      </NavigationMenu>
    </div>
  )
}
TasteAktion
TabBewegt sich durch die Leiste und von einem offenen Trigger in seinen Inhalt.
EnterSpaceÖffnet oder schließt das Menü des fokussierten Triggers.
↑↓Wechselt zwischen Links im offenen Inhalt.
←→Wechselt zwischen Triggern in der Leiste.
EscSchließt das Menü und gibt den Fokus an seinen Trigger zurück.
  • Das Root rendert ein <nav>. Gib ihm ein aria-label, wenn die Seite mehr als eines hat.
  • Trigger sind Buttons, die melden, ob ihr Menü offen ist, und der Fokus kehrt zu ihnen zurück, wenn das Menü schließt.
  • Das Gleiten des Inhalts und das gleitende Highlight werden bei reduzierter Bewegung zu Fades.
PropTypStandard
variantdropdown öffnet unter jedem Trigger. panel öffnet eine Karte in voller Breite unter der Leiste, die stehen bleibt, während der Inhalt gleitet.
"dropdown" | "panel""dropdown"
showSafeAreaZeichnet den sicheren Bereich, der ein Menü offen hält, während der Zeiger hineinfährt. Für Debugging und Demos.
booleanfalse
align
"start" | "center" | "end""start", or "center" for panel
sideOffset
number8
value
any–
defaultValue
any–
onValueChange
(value, details) => void–
delayWartezeit vor dem Öffnen bei Hover, in ms.
number50
closeDelayWartezeit vor dem Schließen, nachdem der Zeiger weggegangen ist, in ms.
number50
orientation
"horizontal" | "vertical""horizontal"
AttributBeschreibung
data-slot="navigation-menu"Die Navigation, mit data-variant.
data-slot="navigation-menu-highlight"Der Hintergrund, der zwischen offenen Triggern gleitet.
data-slot="navigation-menu-popup"Die Karte. Ihre Höhe läuft über --popup-height.
AttributBeschreibung
data-slot="navigation-menu-trigger"Trigger in CSS ansprechen.
data-popup-openVorhanden, solange sein Menü offen ist. Das Chevron dreht sich um.
AttributBeschreibung
data-slot="navigation-menu-content"Der Inhalt eines Menüs, mit data-variant.
data-activation-direction"left", "right", "up" oder "down": wo der vorherige Trigger war. Steuert das Gleiten.
--navigation-menu-link-radiusEckenradius des Links, konzentrisch zur Karte.
PropTypStandard
activeMarkiert die aktuelle Seite.
booleanfalse
render
ReactElement | (props, state) => ReactElement<a>
AttributBeschreibung
data-slot="navigation-menu-link"Links per CSS ansprechen. In der Leiste passen sie sich in der Größe an Trigger an.
data-activeVorhanden auf der aktuellen Seite.