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.
Usage
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
{{-- 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:
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:
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 (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 viaaria-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:
'components' => [
'navigation-menu' => [],
],
Scoped Personalization
<x-wirekit::navigation-menu scope="marketing-nav">
...
</x-wirekit::navigation-menu>
'personalizations' => [
'navigation-menu' => [
'marketing-nav' => [
'base' => 'border-b-2',
],
],
],
Further Reading
- WAI-ARIA Disclosure Pattern -- the authoring pattern for show/hide panels
- WAI-ARIA Navigation Landmark -- semantic navigation regions
- Floating UI -- positioning engine (bundled, ~3.5 KB)
- MDN:
<nav>element - MDN:
aria-current