# Defaults: MainNav (/themes/sdk/reference/navigation/defaults-1)

```ts
import { … } from '@poltura/theme-sdk/navigation';
```

## `MainNav` [#mainnav]

The header's menu bar: the top-level links with their dropdowns or panels and the menu keyboard model.

```ts
MainNav(props: MainNavProps): ReactElement | null
```

**Props:** [`MainNavProps`](/themes/sdk/reference/navigation/types-1#mainnavprops)

<Tab value='Example'>

```tsx
import { MainNav } from '@poltura/theme-sdk/navigation';

export function HeaderMenu() {
  return <MainNav submenu='panel' openOn='click' />;
}
```

</Tab>
<Tab value='Source'>

```tsx title="source/main-nav.tsx"
import { useState, type KeyboardEvent, type ReactElement, type ReactNode } from 'react';
import { ArrowRight, ChevronDown, ChevronRight } from 'lucide-react';
import { useThemeText } from '@poltura/theme-sdk/i18n';
import {
  useMegaMenu,
  useMenu,
  type MainNavProps,
  type MenuLink,
  type SubmenuStyle,
} from '@poltura/theme-sdk/navigation';
import { Link } from '@poltura/theme-sdk/routing';
import { Icon, cn } from '@poltura/theme-sdk/ui';

const VIEW_ALL_KEY = 'nav.viewAll';

const LINK_CLASS =
  'inline-flex whitespace-nowrap rounded-md px-2.5 py-1.5 transition-colors hover:bg-muted text-sm font-medium text-muted-foreground hover:text-foreground items-center gap-1';
const ITEM_LINK_CLASS = 'block rounded-md px-2 py-1.5 text-sm hover:bg-muted';
const PANEL_CLASS =
  'absolute inset-x-0 top-full z-50 border-b border-border bg-popover text-popover-foreground shadow-[var(--overlay-shadow)]';
const DROPDOWN_CLASS =
  'absolute left-0 top-full z-50 mt-1 min-w-[12rem] rounded-md border border-border bg-popover text-popover-foreground shadow-[var(--overlay-shadow)]';

const FOCUSABLE = 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])';

function linkKey(link: MenuLink): string {
  return `${link.href}:${link.label}`;
}

function ItemLabel({ label, icon }: { label: string; icon: string | null }): ReactElement {
  if (icon === null) return <>{label}</>;
  return (
    <span className='flex min-w-0 items-center gap-2'>
      <Icon value={icon} className='size-4 text-muted-foreground' />
      <span className='min-w-0 truncate'>{label}</span>
    </span>
  );
}

function ViewAllLabel(): ReactElement {
  const label = useThemeText()(VIEW_ALL_KEY);
  // A text no language has reads as its key; a translate session leaves its marker after it.
  const rest = label.startsWith(VIEW_ALL_KEY) ? label.slice(VIEW_ALL_KEY.length) : null;
  return <>{rest === null ? label : `View all${rest}`}</>;
}

function focusables(root: HTMLElement): HTMLElement[] {
  return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(
    (element) =>
      element.tabIndex >= 0 &&
      !element.closest('[hidden], [inert], [aria-hidden="true"]') &&
      getComputedStyle(element).display !== 'none' &&
      getComputedStyle(element).visibility !== 'hidden',
  );
}

/** Left and right walk the bar's top-level items, Home and End jump to its ends; Tab still leaves it. */
function onBarKeyDown(event: KeyboardEvent<HTMLElement>): void {
  if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
  const target = event.target;
  if (!(target instanceof HTMLElement)) return;
  if (target.matches('input, textarea, select, [contenteditable="true"]')) return;
  const bar = event.currentTarget;
  const triggers = Array.from(bar.querySelectorAll<HTMLElement>(':scope > ul > li')).flatMap(
    (item) => {
      const first = focusables(item)[0];
      return first ? [first] : [];
    },
  );
  const current = triggers.findIndex((trigger) => trigger.closest('li')?.contains(target));
  if (current < 0) return;
  const atTrigger = target === triggers[current];
  const rtl = getComputedStyle(bar).direction === 'rtl';
  let next: number;
  if (event.key === 'ArrowRight') next = current + (rtl ? -1 : 1);
  else if (event.key === 'ArrowLeft') next = current + (rtl ? 1 : -1);
  else if (atTrigger && event.key === 'Home') next = 0;
  else if (atTrigger && event.key === 'End') next = triggers.length - 1;
  else return;
  event.preventDefault();
  event.stopPropagation();
  triggers[(next + triggers.length) % triggers.length]?.focus();
}

function FlatPanel({
  items,
  parentHref,
  close,
}: {
  items: readonly MenuLink[];
  parentHref: string;
  close: () => void;
}): ReactElement {
  return (
    <div className='grid grid-cols-[1fr_auto] gap-6 p-4'>
      <ul className='flex flex-col gap-1'>
        {items.map((child) => (
          <li key={linkKey(child)}>
            <Link href={child.href} className={ITEM_LINK_CLASS} onClick={close}>
              <ItemLabel label={child.label} icon={child.icon ?? null} />
            </Link>
          </li>
        ))}
      </ul>
      <Link
        href={parentHref}
        className='self-start text-sm font-medium text-foreground hover:underline'
        onClick={close}
      >
        <ViewAllLabel />
      </Link>
    </div>
  );
}

function TwoPanePanel({
  items,
  close,
}: {
  items: readonly MenuLink[];
  close: () => void;
}): ReactElement | null {
  const [activeKey, setActiveKey] = useState(() => {
    const first = items[0];
    return first ? linkKey(first) : '';
  });
  const active = items.find((item) => linkKey(item) === activeKey) ?? items[0];
  if (!active) return null;
  return (
    <div className='grid grid-cols-[minmax(11rem,14rem)_1fr]'>
      <div className='flex flex-col border-r border-border/70 bg-muted/20 p-3'>
        <ul className='flex flex-col gap-0.5'>
          {items.map((item) => {
            const key = linkKey(item);
            const isActive = key === linkKey(active);
            return (
              <li key={key}>
                <button
                  type='button'
                  onPointerEnter={() => setActiveKey(key)}
                  onFocus={() => setActiveKey(key)}
                  className={cn(
                    'flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm transition-colors',
                    isActive
                      ? 'bg-background text-foreground'
                      : 'text-foreground/80 hover:bg-background/60 hover:text-foreground',
                  )}
                >
                  {item.icon == null ? null : (
                    <Icon value={item.icon} className='size-4 text-muted-foreground' />
                  )}
                  <span className='min-w-0 flex-1 truncate'>{item.label}</span>
                  <ChevronRight
                    className={cn(
                      'size-3.5 shrink-0',
                      isActive ? 'text-muted-foreground' : 'text-transparent',
                    )}
                  />
                </button>
              </li>
            );
          })}
        </ul>
      </div>
      <div className='flex min-h-[10rem] flex-col p-4'>
        <Link
          href={active.href}
          className='mb-3 inline-flex items-center gap-1 self-start text-sm font-medium text-foreground hover:underline'
          onClick={close}
        >
          <ViewAllLabel />
          <ArrowRight className='size-3.5' />
        </Link>
        <ul className='flex flex-col gap-0.5'>
          {active.children.map((child) => (
            <li key={linkKey(child)}>
              <Link href={child.href} className={ITEM_LINK_CLASS} onClick={close}>
                <ItemLabel label={child.label} icon={child.icon ?? null} />
              </Link>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

function DropdownList({
  items,
  close,
}: {
  items: readonly MenuLink[];
  close: () => void;
}): ReactElement {
  return (
    <ul className='flex flex-col py-1'>
      {items.map((item) =>
        item.children.length === 0 ? (
          <li key={linkKey(item)}>
            <Link
              href={item.href}
              className='block px-3 py-1.5 text-sm hover:bg-muted'
              onClick={close}
            >
              <ItemLabel label={item.label} icon={item.icon ?? null} />
            </Link>
          </li>
        ) : (
          <li key={linkKey(item)}>
            <Link
              href={item.href}
              className='block px-3 pb-1 pt-2 text-xs font-semibold text-muted-foreground hover:text-foreground'
              onClick={close}
            >
              <ItemLabel label={item.label} icon={item.icon ?? null} />
            </Link>
            <ul className='flex flex-col'>
              {item.children.map((child) => (
                <li key={linkKey(child)}>
                  <Link
                    href={child.href}
                    className='block py-1.5 pl-6 pr-3 text-sm hover:bg-muted'
                    onClick={close}
                  >
                    <ItemLabel label={child.label} icon={child.icon ?? null} />
                  </Link>
                </li>
              ))}
            </ul>
          </li>
        ),
      )}
    </ul>
  );
}

function PanelBody({
  submenu,
  items,
  parentHref,
  close,
}: {
  submenu: SubmenuStyle;
  items: readonly MenuLink[];
  parentHref: string;
  close: () => void;
}): ReactElement | null {
  if (submenu === 'dropdown') return <DropdownList items={items} close={close} />;
  if (items.some((item) => item.children.length > 0)) {
    return <TwoPanePanel items={items} close={close} />;
  }
  return <FlatPanel items={items} parentHref={parentHref} close={close} />;
}

function NavItem({
  link,
  submenu,
  openOn,
  flat,
  linkClassName,
}: {
  link: MenuLink;
  submenu: SubmenuStyle;
  openOn: 'hover' | 'click';
  flat: boolean;
  linkClassName: string | undefined;
}): ReactElement {
  const itemOpen = link.opens ?? openOn;
  const menu = useMegaMenu({ openOn: itemOpen });
  const children = flat ? [] : link.children;
  const itemClass = cn(LINK_CLASS, linkClassName);
  if (children.length === 0) {
    return (
      <li className='shrink-0'>
        <Link href={link.href} className={itemClass}>
          {link.label}
        </Link>
      </li>
    );
  }
  const chevron: ReactNode = (
    <ChevronDown className={cn('size-3.5 transition-transform', menu.open && 'rotate-180')} />
  );
  const body = (
    <PanelBody submenu={submenu} items={children} parentHref={link.href} close={menu.close} />
  );
  return (
    <li className={cn('shrink-0', submenu === 'panel' ? 'static' : 'relative')}>
      <div {...menu.rootProps}>
        {itemOpen === 'click' ? (
          <button type='button' className={itemClass} {...menu.triggerProps}>
            {link.label}
            {chevron}
          </button>
        ) : (
          <Link href={link.href} className={itemClass} {...menu.triggerProps}>
            {link.label}
            {chevron}
          </Link>
        )}
        {submenu === 'panel' ? (
          <div {...menu.panelProps} data-theme-nav-panel='' className={PANEL_CLASS}>
            <div className='mx-auto w-full max-w-6xl px-4'>{body}</div>
          </div>
        ) : (
          <div {...menu.panelProps} data-theme-nav-panel='' className={DROPDOWN_CLASS}>
            {body}
          </div>
        )}
      </div>
    </li>
  );
}

export function MainNav({
  links,
  submenu = 'dropdown',
  openOn = 'hover',
  flat = false,
  className,
  linkClassName,
}: MainNavProps): ReactElement | null {
  const primary = useMenu('primary');
  const shown = links ?? primary;
  if (shown.length === 0) return null;
  return (
    // Not a positioning context on purpose: full-width panels anchor to the sticky header.
    // oxlint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- arrow keys between the links inside, which hold the focus
    <nav
      className={cn('min-w-0 w-full', className)}
      aria-label='Primary'
      data-theme-nav-bar=''
      onKeyDown={onBarKeyDown}
    >
      <ul className='flex min-w-0 flex-wrap items-center gap-0.5 overflow-visible'>
        {shown.map((link) => (
          <NavItem
            key={linkKey(link)}
            link={link}
            submenu={submenu}
            openOn={openOn}
            flat={flat}
            linkClassName={linkClassName}
          />
        ))}
      </ul>
    </nav>
  );
}
```

</Tab>


**See also:** [`useMenu`](/themes/sdk/reference/navigation/hooks#usemenu), [`NavMenu`](/themes/sdk/reference/navigation/parts#navmenu)
