Skip to content

Navigation Menu

A horizontal or vertical navigation bar with dropdown content panels that appear on hover or click.

Features

  • ✅ Hover intent with configurable open/close delays
  • ✅ Smart positioning with auto-flip and shift
  • ✅ Optional arrow pointing to trigger
  • ✅ Keyboard navigation between triggers and links
  • ✅ Only one content panel visible at a time
  • ✅ Touch device support (click instead of hover)
  • ✅ Roving tabindex for accessible trigger navigation
  • ✅ Full WAI-ARIA menubar pattern
  • ✅ Directional data attributes for CSS animations

Basic Usage

With Arrow

Add an arrow that points to the trigger element, just like popovers.

Custom Delays

Configure hover intent delays to control how quickly menus open and close.

Vertical Orientation

Use orientation: 'vertical' for a vertical sidebar-style navigation. The dropdown opens to the right of each trigger.

With x-model

Control the active value from outside using Alpine's x-model.

Active:

API Reference

Root Props

PropertyTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Navigation direction
delaynumber50Hover-to-open delay in milliseconds
closeDelaynumber50Hover-to-close delay in milliseconds
valuestring | nullnullCurrently active item value
placementPlacement'bottom'Dropdown position relative to trigger
strategy'absolute' | 'fixed''absolute'CSS positioning strategy for the dropdown
offsetnumber8Distance from trigger in pixels

Placement Values

  • top, top-start, top-end
  • bottom, bottom-start, bottom-end
  • left, left-start, left-end
  • right, right-start, right-end

Parts

PartDescription
x-navigation-menu:listMenu items wrapper (menubar role)
x-navigation-menu:itemIndividual menu entry (scope provider). Value: unique string identifier
x-navigation-menu:triggerButton that opens dropdown on hover/click
x-navigation-menu:positionerSingle positioning wrapper for all content (managed by Floating UI)
x-navigation-menu:viewportContainer inside positioner that holds all content panels
x-navigation-menu:contentDropdown content container (menu role). Value: unique string identifier matching an item
x-navigation-menu:linkNavigation anchor inside content (menuitem role)
x-navigation-menu:arrowOptional arrow pointing to trigger (inside positioner)
x-navigation-menu:indicatorOptional visual indicator (inside item scope)

Scope Data ($item)

Available inside x-navigation-menu:item and its children via $item:

PropertyTypeDescription
valuestringThis item's unique identifier
triggerIdstringGenerated trigger element ID
isActivebooleanWhether this item's dropdown is open
hasContentbooleanWhether this item has registered content
open()functionOpen this item's dropdown
close()functionClose the dropdown immediately

Data Attributes

AttributeValuesDescription
data-stateopen | closedCurrent open/closed state
data-scopenavigation-menuIdentifies component scope
data-partPart nameIdentifies component part
data-orientationhorizontal | verticalNavigation direction (on root and list)
data-valueItem valueUnique item identifier (on item and trigger)
data-activation-directionforward | backward | noneDirection of last item switch (on content, viewport)

Events

EventDetailDescription
value-change{ value: string | null, previousValue: string | null }Fired when active item changes

Keyboard Interactions

KeyDescription
Arrow RightFocus next trigger (horizontal mode)
Arrow LeftFocus previous trigger (horizontal mode)
Arrow DownFocus next trigger (vertical) or first content link (horizontal, when open)
Arrow UpFocus previous trigger (vertical mode)
HomeFocus first trigger
EndFocus last trigger
Enter / SpaceToggle dropdown content
EscapeClose dropdown and return focus to trigger
TabMove focus through content links

Accessibility

  • Follows WAI-ARIA menubar pattern
  • Root has role="navigation" with aria-label
  • List has role="menubar" with aria-orientation
  • Triggers have role="menuitem" with aria-expanded and aria-controls
  • Content has role="menu" linked via aria-labelledby
  • Links have role="menuitem" with tabindex="-1"
  • Roving tabindex on triggers ensures proper Tab key behavior
  • Touch devices use click interaction instead of hover

Built with Alpine.js and inspired by Zag.js