---
title: Navigation Menu
description: Accessible navigation menu with mega-menu support
visibility: guest
draft: false
---

# Navigation Menu

The `<x-wirekit::navigation-menu>` component creates a top-level navigation with flyout panels (mega menus). It combines simple links with rich dropdown panels triggered by hover or click, following the [WAI-ARIA Disclosure pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/).

## Usage

:::preview{title="Navigation Menu with Flyout Panels"}
<x-wirekit::navigation-menu>
    <x-wirekit::navigation-menu.item trigger="Products">
        <x-wirekit::navigation-menu.link href="/products/wirekit">
            WireKit — UI component library for Laravel Livewire
        </x-wirekit::navigation-menu.link>
        <x-wirekit::navigation-menu.link href="/products/starter-kits">
            Starter Kits — Pre-built Laravel application templates
        </x-wirekit::navigation-menu.link>
    </x-wirekit::navigation-menu.item>
    <x-wirekit::navigation-menu.item href="/pricing">Pricing</x-wirekit::navigation-menu.item>
    <x-wirekit::navigation-menu.item href="/blog">Blog</x-wirekit::navigation-menu.item>
</x-wirekit::navigation-menu>
:::

## Simple Links vs Flyout Triggers

Items come in two modes:

- **Simple link:** pass `href` -- renders a plain navigation link
- **Flyout trigger:** pass `trigger` -- renders a disclosure button that opens a panel on hover or click

```blade
{{-- Simple link -- navigates directly --}}
<x-wirekit::navigation-menu.item href="/pricing">Pricing</x-wirekit::navigation-menu.item>

{{-- Flyout trigger -- opens a panel --}}
<x-wirekit::navigation-menu.item trigger="Products">
    <div>Panel content here</div>
</x-wirekit::navigation-menu.item>
```

## Active State

Mark the currently active item with the `active` prop on links inside panels:

:::preview{title="Active Link in Flyout Panel"}
<x-wirekit::navigation-menu>
    <x-wirekit::navigation-menu.item trigger="Resources">
        <x-wirekit::stack gap="xs" style="width: 16rem">
            <x-wirekit::navigation-menu.link href="#" :active="true">Documentation</x-wirekit::navigation-menu.link>
            <x-wirekit::navigation-menu.link href="#">Blog</x-wirekit::navigation-menu.link>
            <x-wirekit::navigation-menu.link href="#">Changelog</x-wirekit::navigation-menu.link>
        </x-wirekit::stack>
    </x-wirekit::navigation-menu.item>
    <x-wirekit::navigation-menu.item href="#">Pricing</x-wirekit::navigation-menu.item>
</x-wirekit::navigation-menu>
:::

In a real Laravel app, pair the `active` prop with `request()->is('…')` (or `request()->routeIs('…')`) so the current page is highlighted automatically — e.g. `:active="request()->is('docs*')"`. Active links receive an accent underline and use `aria-current="page"`.

## Mega Menu Layout

For complex navigation with multiple columns, use CSS Grid inside the panel:

:::preview{title="Mega Menu with Columns"}
<x-wirekit::navigation-menu>
    <x-wirekit::navigation-menu.item trigger="Solutions">
        <div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; width: 44rem;">
            <div>
                <h3 style="font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem; color: var(--color-wk-text-muted);">By Industry</h3>
                <x-wirekit::navigation-menu.link href="#">SaaS</x-wirekit::navigation-menu.link>
                <x-wirekit::navigation-menu.link href="#">E-Commerce</x-wirekit::navigation-menu.link>
                <x-wirekit::navigation-menu.link href="#">Fintech</x-wirekit::navigation-menu.link>
            </div>
            <div>
                <h3 style="font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem; color: var(--color-wk-text-muted);">By Team Size</h3>
                <x-wirekit::navigation-menu.link href="#">Startup</x-wirekit::navigation-menu.link>
                <x-wirekit::navigation-menu.link href="#">Scale-up</x-wirekit::navigation-menu.link>
                <x-wirekit::navigation-menu.link href="#">Enterprise</x-wirekit::navigation-menu.link>
            </div>
            <div>
                <h3 style="font-weight: 600; margin-bottom: 0.5rem; font-size: 0.875rem; color: var(--color-wk-text-muted);">Resources</h3>
                <x-wirekit::navigation-menu.link href="#">Case Studies</x-wirekit::navigation-menu.link>
                <x-wirekit::navigation-menu.link href="#">Webinars</x-wirekit::navigation-menu.link>
                <x-wirekit::navigation-menu.link href="#">White Papers</x-wirekit::navigation-menu.link>
            </div>
        </div>
    </x-wirekit::navigation-menu.item>
    <x-wirekit::navigation-menu.item href="#">Pricing</x-wirekit::navigation-menu.item>
</x-wirekit::navigation-menu>
:::

## Behavior

- **Hover-to-open** with configurable delay prevents accidental panel triggers
- **Bridge gap** keeps the panel open while the cursor travels from trigger to panel
- **[Floating UI](https://floating-ui.com/)** (bundled ~3.5 KB) positions panels below their triggers with flip and shift
- **Click outside** closes any open panel
- **Livewire SPA** navigation (`wire:navigate`) automatically closes open panels
- **Transitions** use opacity + slide for smooth panel reveal

## Props

### `<x-wirekit::navigation-menu>`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `scope` | `string\|null` | `null` | Scoped personalization key |

### `<x-wirekit::navigation-menu.item>`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `trigger` | `string\|null` | `null` | Label text for flyout trigger (enables panel mode) |
| `href` | `string\|null` | `null` | URL for simple link mode (no panel) |
| `scope` | `string\|null` | `null` | Scoped personalization key |

### `<x-wirekit::navigation-menu.link>`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | `'#'` | Link destination |
| `active` | `bool` | `false` | Highlights as current page (sets `aria-current="page"`) |
| `scope` | `string\|null` | `null` | Scoped personalization key |

## Sub-Components

| Component | Purpose |
| --- | --- |
| `navigation-menu.item` | Top-level trigger (flyout) or simple link |
| `navigation-menu.link` | Navigation link inside a flyout panel |

## Accessibility

- Navigation wrapper: `<nav aria-label="Main">` semantic landmark
- Flyout triggers: `aria-haspopup="menu"`, `aria-expanded` (dynamic)
- Panels: `role="menu"`, linked via `aria-controls`
- Links inside panels: standard `<a>` elements, `aria-current="page"` when active
- **Roving tabindex** on top-level items: only the focused item has `tabindex="0"`
- **Hover intent:** panels open on hover with a short delay to prevent accidental triggers; panels stay open when the cursor moves between trigger and panel (bridge delay)
- **Click also works** as a fallback for touch devices
- Chevron indicators on flyout triggers are `aria-hidden="true"` (decorative)

## Keyboard Interaction

| Key | Context | Action |
| --- | --- | --- |
| Tab | Top-level items | Move focus through the navigation items |
| Arrow Left / Right | Top-level items | Move focus between navigation items |
| Home / End | Top-level items | Jump to first / last navigation item |
| Enter / Space | Flyout trigger | Toggle the panel open/closed |
| Arrow Down | Flyout trigger (panel closed) | Open panel, focus first link |
| Tab | Open panel | Move focus through links inside the panel |
| Escape | Open panel | Close panel, return focus to trigger |

## Pitfalls

- **Don't use navigation-menu for app-internal commands.** It's purpose-built for site navigation (top-level sections). Reach for `<x-wirekit::menubar>` for File > Edit > View patterns.
- **Don't bury the active page deep in a submenu.** WCAG 2.4.8 (Location) is best served by surfacing the current section visibly — keep the top-level link active even when a child is shown.

## Design Tokens

| Element | Token |
| --- | --- |
| Bar background | `--color-wk-bg-elevated` |
| Item text | `--color-wk-text` |
| Item hover text | `--color-wk-accent` |
| Active indicator | `--color-wk-accent` |
| Panel background | `--color-wk-bg-elevated` |
| Panel border | `--color-wk-border` / `--border-wk-width` |
| Panel radius | `--radius-wk-lg` |
| Panel shadow | `--shadow-wk-lg` |
| Panel padding | `--padding-wk-x-md` |
| Link text | `--color-wk-text` |
| Link hover bg | `--color-wk-bg-subtle` |
| Link active text | `--color-wk-accent` |
| Link description | `--color-wk-text-muted` |
| Font family | `--font-wk-sans` |
| Font size | `--text-wk-md` |
| Transition | `--transition-wk-duration` |

## Personalization

Override defaults in `config/wirekit.php`:

```php
'components' => [
    'navigation-menu' => [],
],
```

### Scoped Personalization

```blade
<x-wirekit::navigation-menu scope="marketing-nav">
    ...
</x-wirekit::navigation-menu>
```

```php
'personalizations' => [
    'navigation-menu' => [
        'marketing-nav' => [
            'base' => 'border-b-2',
        ],
    ],
],
```

## Further Reading

- [WAI-ARIA Disclosure Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) -- the authoring pattern for show/hide panels
- [WAI-ARIA Navigation Landmark](https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/) -- semantic navigation regions
- [Floating UI](https://floating-ui.com/) -- positioning engine (bundled, ~3.5 KB)
- [MDN: `<nav>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
- [MDN: `aria-current`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current)
