Skip to main content
WireKit
Copy for LLM

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

Navigation Menu with Flyout Panels

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:

Active Link in Flyout Panel

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:

Mega Menu with Columns

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
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 navigation"> semantic landmark
  • Flyout triggers: aria-expanded (dynamic) plus aria-controls naming the panel they reveal. Deliberately no aria-haspopup and no role="menu" — this is a disclosure navigation menu: the button reveals a region of links in place, it does not open a menu or a dialog, and announcing one that never appears is worse than announcing nothing
  • Panels: a plain region of links carrying the id the trigger's aria-controls names
  • Links inside panels: standard <a> elements, aria-current="page" when active
  • Every top-level item stays in the tab sequence — no roving tabindex. A bar of navigation links is not a composite widget, so a reader tabs across it the way they tab across any other set of links; the arrow keys below are an addition, not a replacement
  • 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; from the last one, close the panel and continue at the next top-level item
Shift + Tab First link in an open panel Close the panel and return focus to its trigger
Escape Open panel Close panel, return focus to trigger

The panel is rendered at the end of the document so its position: fixed box escapes any transformed or clipped ancestor, which means the browser's own tab order no longer matches what you see. The component supplies the missing edges itself: Tab off the trigger steps into the panel, and Tab off the panel's last focusable element steps back out to the bar. Anything focusable you put in the slot — a search field, a call-to-action button — joins that sequence in markup order, so there is nothing extra to wire up.

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

Was this page helpful?

Voting requires cookies or local storage. What we store