Navigation Menu
Top navigation with hover-driven dropdown panels. Supports plain links (no dropdown) and mega-menu style panels. Configurable hover delays for both open and close.
Features
Hover-to-open with configurable delay.
Skip-delay window for moving from trigger to content.
Switching between open menus is instant (no second delay).
Mix plain links and dropdown triggers in the same bar.
Active-link styling via
data-active.Renders as a real
<nav> element with aria-label.Example
Anatomy
import { NavigationMenu } from '@wire-ui/react'
<NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Link href="/" active>Home</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Item value="products">
<NavigationMenu.Trigger>Products</NavigationMenu.Trigger>
<NavigationMenu.Content>
<NavigationMenu.Link href="/design">Design</NavigationMenu.Link>
<NavigationMenu.Link href="/develop">Develop</NavigationMenu.Link>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="/pricing">Pricing</NavigationMenu.Link>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>Styling
Trigger and Content expose data-state for open/closed styling. Link exposes data-active when its active prop is true.
<NavigationMenu.Trigger className="data-[state=open]:bg-[#f5f5f5]">
Products
</NavigationMenu.Trigger>
<NavigationMenu.Link active className="data-[active]:bg-black data-[active]:text-white">
Home
</NavigationMenu.Link>Using data attributes
[data-state="open"] { background: #f5f5f5; }
[data-state="closed"] { /* default */ }
[data-active] { background: #000; color: #fff; }Root props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | null | — | Controlled open item value |
defaultValue | string | null | null | Initial open item (uncontrolled) |
onValueChange | (value: string | null) => void | — | Called when the open item changes |
delayDuration | number | 100 | Hover-to-open delay in ms |
skipDelayDuration | number | 300 | Time (ms) to move from trigger → content before closing |
aria-label | string | 'Main' | Label for the rendered <nav> |
Item props
| Prop | Type | Description |
|---|---|---|
value | string | Unique id — required only when the item has a Trigger/Content pair |
Trigger props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | Disables this trigger |
Link props
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | false | Marks the link as active; adds data-active and aria-current="page" |
Data attributes
| Attribute | Element | Values |
|---|---|---|
data-state | Trigger, Content | "open" / "closed" |
data-active | Link | Present when active is true |
data-disabled | Trigger | Present when disabled |
data-hover / data-active / data-focus-visible | Trigger | Standard interactive states |
Root renders as <nav> with aria-label. Trigger sets aria-haspopup="menu" and aria-expanded. Content sets role="menu". Link sets aria-current="page" when active.
Accessibility
- Open delay (
delayDuration) prevents flickering when sweeping across the bar. - Close delay (
skipDelayDuration) gives the user time to move from trigger to content. - When one menu is open, hovering a different trigger switches immediately (no second open delay).
- Outside click or Escape closes the open menu.
Keyboard Interactions
| Key | Description |
|---|---|
| Enter | Activates the focused trigger or link. |
| Space | Activates the focused trigger. |
| Tab | Moves focus to the next trigger or link. |
| Escape | Closes the open menu. |
Internals
Since 0.3.0, the hover-intent open/close timers are delegated to useTimeout. No public API change — the delayDuration / skipDelayDuration props behave identically.
Last updated on