PixelNavigationMenu

Accessible nav landmark with optional mega-panel submenus, keyboard navigation, and surface-aware styling.

  • Horizontal or vertical orientation
  • Optional shared viewport panel or inline per-item panels
  • WAI-ARIA disclosure navigation: each panel follows its button in the tab order, with Arrow/Home/End/Escape keys
  • Surface-aware (pixel / linear)
  • SSR-safe nav landmark
Status
stable
Category
navigation
Since
v1.9.0

API

import { PixelNavigationMenu } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
items (required)PixelNavigationMenuItem[]—The items, in order.
orientation'horizontal' | 'vertical''horizontal'Items in a row or a column; also the arrow keys that move between them.
viewportbooleantrueOne shared panel below the list, instead of a panel under each item.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.
ariaLabelstring'Main navigation'Accessible name for the nav landmark. Required when more than one nav lands on the same page (WCAG 2.4.6). Defaults to "Main navigation".

Also takes the native attributes and event handlers of its element (HTMLAttributes<HTMLElement>), except children.

ref points to its element (HTMLElement).

Accessibility

WCAG target: 2.1 AA

  • disclosure
  • link

Follows the WAI-ARIA disclosure navigation pattern, without menu roles (those are for application menus): a <nav> landmark — give it a unique ariaLabel when the page has more than one (WCAG 2.4.6) — holding a list of links, and of buttons with aria-expanded and aria-controls for the items with content. Each panel is a plain container rendered right after its button, inside the same list item, so Tab moves from the button into the open panel; the shared viewport is only drawn below the whole list. A click, or Enter / Space on the button, toggles the panel; focus alone never opens it. A mouse pointing at an item opens its panel — touch and pen pointers do not, so a tap opens it once — and the pointer leaving the menu closes that panel, unless a click on its button kept it open: a panel opened by a click stays open until a click on its button or another one, or Escape. A panel that closes while focus is inside it hands focus back to its button. An item with both an href and content is a button whose activation toggles the panel instead of navigating.

Keyboard

KeyActionWhen
TabMove to the next link or button; from the button of an open panel, into the panel—
Shift+TabMove to the previous link or button—
EnterToggle the panel of a button, or follow a link; invokes onSelect—
SpaceToggle the panel of a button; invokes onSelect—
ArrowRightFocus next item, wrapping roundhorizontal
ArrowLeftFocus previous item, wrapping roundhorizontal
ArrowDownFocus the first link of the open panelhorizontal, on the button of an open panel
ArrowDownFocus next item, wrapping roundvertical
ArrowUpFocus previous item, wrapping roundvertical
HomeFocus first item—
EndFocus last item—
EscapeClose the open panel; focus on its button or inside it returns to the button—

Usage

import { PixelNavigationMenu } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelNavigationMenu
      items={[
        { label: 'Home', href: '#home' },
        {
          label: 'Products',
          content: (
            <div className="grid gap-2 text-sm text-retro-text">
              <a href="#analytics" className="hover:underline">Analytics</a>
              <a href="#dashboard" className="hover:underline">Dashboard</a>
              <a href="#reports" className="hover:underline">Reports</a>
            </div>
          ),
        },
        { label: 'Docs', href: '#docs' },
        { label: 'Pricing', href: '#pricing' },
      ]}
    />
  )
}

Examples

Default

import { PixelNavigationMenu } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelNavigationMenu
      items={[
        { label: 'Home', href: '#home' },
        {
          label: 'Products',
          content: (
            <div className="grid gap-2 text-sm text-retro-text">
              <a href="#analytics" className="hover:underline">Analytics</a>
              <a href="#dashboard" className="hover:underline">Dashboard</a>
              <a href="#reports" className="hover:underline">Reports</a>
            </div>
          ),
        },
        { label: 'Docs', href: '#docs' },
        { label: 'Pricing', href: '#pricing' },
      ]}
    />
  )
}

Vertical

import { PixelNavigationMenu } from '@pxlkit/ui-kit';

export function Vertical() {
  return (
    <PixelNavigationMenu
      orientation="vertical"
      items={[
        { label: 'Overview', href: '#overview' },
        { label: 'Settings', href: '#settings' },
        { label: 'Billing', href: '#billing' },
      ]}
    />
  )
}

Inline Panels

import { PixelNavigationMenu } from '@pxlkit/ui-kit';

export function InlinePanels() {
  return (
    <PixelNavigationMenu
      viewport={false}
      items={[
        { label: 'Home', href: '#home' },
        {
          label: 'Resources',
          content: (
            <div className="grid gap-2 text-sm text-retro-text">
              <a href="#guides" className="hover:underline">Guides</a>
              <a href="#api" className="hover:underline">API Reference</a>
            </div>
          ),
        },
      ]}
    />
  )
}