# Top navigation

The header bar of a site or app, with regions for identity, navigation, and actions. Arrange the regions with your own CSS; this page has recipes to start from.

```tsx
// demo.tsx
import {SearchIcon} from '@earendil-works/elanor-icons-react';
import {Link, TopNavigation} from '@earendil-works/elanor-react';

import styles from './top-navigation.module.css';

export default function TopNavigationDemo() {
  return (
    <TopNavigation.Root>
      <TopNavigation.Identity>
        <Link href="#">Acme</Link>
      </TopNavigation.Identity>
      <TopNavigation.Navigation>
        <nav aria-label="Main" className={styles.links}>
          <TopNavigation.Link href="#">Product</TopNavigation.Link>
          <TopNavigation.Link href="#">Pricing</TopNavigation.Link>
          <TopNavigation.Link href="#" active>
            Docs
          </TopNavigation.Link>
        </nav>
      </TopNavigation.Navigation>
      <TopNavigation.Actions>
        <TopNavigation.ActionButton aria-label="Search">
          <SearchIcon aria-hidden />
        </TopNavigation.ActionButton>
        <Link href="#">Sign in</Link>
      </TopNavigation.Actions>
    </TopNavigation.Root>
  );
}
```

```css
/* top-navigation.module.css */
.links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--semantic-dimension-space-gap-medium);
}
```

## Usage

```tsx
import {TopNavigation} from '@earendil-works/elanor-react';

<TopNavigation.Root>
  <TopNavigation.Identity />
  <TopNavigation.Navigation>
    <nav aria-label="Main">
      <TopNavigation.Link href="/docs" active />
    </nav>
  </TopNavigation.Navigation>
  <TopNavigation.Actions>
    <TopNavigation.ActionButton aria-label="Search" />
  </TopNavigation.Actions>
</TopNavigation.Root>;
```

Root renders a `header`; the regions render `div`s. Put links inside a `nav` with an `aria-label`, or use [Navigation menu](/components/navigation-menu.md) for submenus.

`Link` renders an `a` in `semantic.color.text.secondary`, `semantic.color.text.primary` on hover; `active` marks the current page or section with `aria-current="page"` in primary text. It passes its props and ref through, so a router can wrap it. `ActionButton` is an icon button for the bar's actions, such as search or a theme switch, styled like the menu's trigger: `semantic.dimension.size.large` square, its icon `semantic.dimension.size.icon`. It renders a `button`; name it with `aria-label`. In a [Layout](/components/layout.md), merge Root into the header with `<Layout.Header render={<TopNavigation.Root />}>`.

By default, the regions share one row, with free space between them, and wrap when space runs out. The navigation sits centered only when identity and actions have the same width; in the demo above, it is pushed left.

## Height

Root is `semantic.dimension.size.topNavigation` (64px) tall and centers its content, so the bar needs no block padding. With `box-sizing: border-box`, a bottom border you add counts toward the 64px. The height is fixed so that offsets below the bar can come from the same token: a sticky header and the page's scroll padding, so anchor jumps and focused elements stay clear of it. The token is in rem, so the bar grows with the user's font size.

Keep the bar to one row: on narrow screens, move links into a [Drawer](/components/drawer.md) or a menu, as in [Responsive navigation](#responsive-navigation). Layouts that need more rows, like the stacked and centered recipes below, set `block-size: auto`; derive offsets for such headers from their measured height instead of the token.

```css
.header {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--semantic-number-layer-sticky);
  border-block-end: var(--semantic-dimension-border-width-small) solid
    var(--semantic-color-border-secondary);
  background: var(--semantic-color-background-canvas);
}

html {
  scroll-padding-block-start: var(--semantic-dimension-size-top-navigation);
}
```

## Centered

Equal side columns keep the navigation centered, whatever the widths of identity and actions. Compare it with the demo at the top of the page. In narrow containers, the links move to their own row.

```tsx
// centered.tsx
import {Link, TopNavigation} from '@earendil-works/elanor-react';

import styles from './centered.module.css';

export default function TopNavigationCentered() {
  return (
    <div className={styles.frame}>
      <TopNavigation.Root className={styles.header}>
        <TopNavigation.Identity>
          <Link href="#">Acme</Link>
        </TopNavigation.Identity>
        <TopNavigation.Navigation className={styles.navigation}>
          <nav aria-label="Main" className={styles.links}>
            <TopNavigation.Link href="#">Product</TopNavigation.Link>
            <TopNavigation.Link href="#">Pricing</TopNavigation.Link>
            <TopNavigation.Link href="#" active>
              Docs
            </TopNavigation.Link>
          </nav>
        </TopNavigation.Navigation>
        <TopNavigation.Actions className={styles.actions}>
          <Link href="#">Contact sales</Link>
          <Link href="#">Sign in</Link>
        </TopNavigation.Actions>
      </TopNavigation.Root>
    </div>
  );
}
```

```css
/* centered.module.css */
.frame {
  container: navigation / inline-size;
}

.header {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Two rows in narrow containers: opt out of the bar's fixed height. */
  block-size: auto;
  padding-block: var(--semantic-dimension-space-padding-xx-large);
}

.navigation {
  grid-column: 1 / -1;
  grid-row: 2;
  justify-content: center;
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--semantic-dimension-space-gap-medium);
}

.actions {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
}

/* Container queries can't use var(), so the threshold is the token's resolved value. */
@container navigation (width >= 40rem /* token: semantic.dimension.breakpoint.xSmall */) {
  .header {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    block-size: var(--semantic-dimension-size-top-navigation);
    padding-block: 0;
  }

  .navigation {
    grid-column: 2;
    grid-row: 1;
  }

  .actions {
    grid-column: 3;
  }
}
```

## Stacked

Identity and actions on one row, navigation on the row below.

```tsx
// stacked.tsx
import {Link, TopNavigation} from '@earendil-works/elanor-react';

import styles from './stacked.module.css';

export default function TopNavigationStacked() {
  return (
    <TopNavigation.Root className={styles.header}>
      <TopNavigation.Identity>
        <Link href="#">Acme</Link>
      </TopNavigation.Identity>
      <TopNavigation.Actions className={styles.actions}>
        <Link href="#">Sign in</Link>
      </TopNavigation.Actions>
      <TopNavigation.Navigation className={styles.navigation}>
        <nav aria-label="Main" className={styles.links}>
          <TopNavigation.Link href="#">Product</TopNavigation.Link>
          <TopNavigation.Link href="#">Pricing</TopNavigation.Link>
          <TopNavigation.Link href="#" active>
            Docs
          </TopNavigation.Link>
        </nav>
      </TopNavigation.Navigation>
    </TopNavigation.Root>
  );
}
```

```css
/* stacked.module.css */
.header {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Two rows: opt out of the bar's fixed height. */
  block-size: auto;
  padding-block: var(--semantic-dimension-space-padding-xx-large);
}

.actions {
  justify-self: end;
}

.navigation {
  grid-column: 1 / -1;
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--semantic-dimension-space-gap-medium);
}
```

## Menu

For narrow screens, `Menu` collapses the navigation into a menu below the bar. `MenuTrigger` shows the menu icon, and the close icon while the menu is open; `MenuPopup` fills the window below `semantic.dimension.size.topNavigation`. It is built on Base UI's Dialog without modality: Escape, the trigger, and choosing a link close it, and the bar's other controls, such as a theme switcher, stay usable and leave it open. Put the panels in a `nav` named like the navigation it replaces.

```tsx
// menu.tsx
import {Link, Text, TopNavigation} from '@earendil-works/elanor-react';
import {useRef} from 'react';

import styles from './menu.module.css';

const start = ['Introduction', 'Installation'];
const guides = ['Deploying a project', 'Rollbacks'];
const reference = ['Command-line tool', 'Configuration file'];

export default function TopNavigationMenuDemo() {
  const frameRef = useRef<HTMLDivElement>(null);
  return (
    <div ref={frameRef} className={styles.frame}>
      <TopNavigation.Root className={styles.header}>
        <TopNavigation.Identity>
          <Link href="#">Acme</Link>
        </TopNavigation.Identity>
        <TopNavigation.Actions>
          <TopNavigation.Menu defaultPanel="main">
            <TopNavigation.MenuTrigger aria-label="Navigation menu" />
            <TopNavigation.MenuPopup aria-label="Navigation menu" container={frameRef}>
              <nav aria-label="Main">
                <TopNavigation.MenuPanel panel="main">
                  <TopNavigation.MenuList>
                    <TopNavigation.MenuItem>
                      <TopNavigation.MenuPanelTrigger panel="docs" active>
                        Docs
                      </TopNavigation.MenuPanelTrigger>
                    </TopNavigation.MenuItem>
                    <TopNavigation.MenuItem>
                      <TopNavigation.MenuLink href="#">Packages</TopNavigation.MenuLink>
                    </TopNavigation.MenuItem>
                    <TopNavigation.MenuItem>
                      <TopNavigation.MenuLink href="#">News</TopNavigation.MenuLink>
                    </TopNavigation.MenuItem>
                  </TopNavigation.MenuList>
                </TopNavigation.MenuPanel>
                <TopNavigation.MenuPanel panel="docs" parent="main" backLabel="Main menu">
                  <TopNavigation.MenuGroup>
                    <TopNavigation.MenuGroupLabel>Get started</TopNavigation.MenuGroupLabel>
                    <TopNavigation.MenuList>
                      {start.map((page) => (
                        <TopNavigation.MenuItem key={page}>
                          <TopNavigation.MenuLink href="#">{page}</TopNavigation.MenuLink>
                        </TopNavigation.MenuItem>
                      ))}
                    </TopNavigation.MenuList>
                  </TopNavigation.MenuGroup>
                  <TopNavigation.MenuGroup>
                    <TopNavigation.MenuGroupLabel>Learn</TopNavigation.MenuGroupLabel>
                    <TopNavigation.MenuList>
                      <TopNavigation.MenuItem>
                        <TopNavigation.MenuCollapsible defaultOpen>
                          <TopNavigation.MenuCollapsibleTrigger>
                            Guides
                          </TopNavigation.MenuCollapsibleTrigger>
                          <TopNavigation.MenuCollapsiblePanel>
                            <TopNavigation.MenuList>
                              {guides.map((page) => (
                                <TopNavigation.MenuItem key={page}>
                                  <TopNavigation.MenuLink href="#" active={page === 'Rollbacks'}>
                                    {page}
                                  </TopNavigation.MenuLink>
                                </TopNavigation.MenuItem>
                              ))}
                            </TopNavigation.MenuList>
                          </TopNavigation.MenuCollapsiblePanel>
                        </TopNavigation.MenuCollapsible>
                      </TopNavigation.MenuItem>
                      <TopNavigation.MenuItem>
                        <TopNavigation.MenuCollapsible>
                          <TopNavigation.MenuCollapsibleTrigger>
                            Reference
                          </TopNavigation.MenuCollapsibleTrigger>
                          <TopNavigation.MenuCollapsiblePanel>
                            <TopNavigation.MenuList>
                              {reference.map((page) => (
                                <TopNavigation.MenuItem key={page}>
                                  <TopNavigation.MenuLink href="#">{page}</TopNavigation.MenuLink>
                                </TopNavigation.MenuItem>
                              ))}
                            </TopNavigation.MenuList>
                          </TopNavigation.MenuCollapsiblePanel>
                        </TopNavigation.MenuCollapsible>
                      </TopNavigation.MenuItem>
                    </TopNavigation.MenuList>
                  </TopNavigation.MenuGroup>
                </TopNavigation.MenuPanel>
              </nav>
            </TopNavigation.MenuPopup>
          </TopNavigation.Menu>
        </TopNavigation.Actions>
      </TopNavigation.Root>
      <div className={styles.content}>
        <Text>Open the menu, then open Docs.</Text>
      </div>
    </div>
  );
}
```

```css
/* menu.module.css */
.frame {
  /* Demo only: the fixed menu is placed within the frame. Layout containment only: with paint
     containment, WebKit misses clicks on the menu's panels. */
  contain: layout;
  /* stylelint-disable-next-line design-tokens/no-hardcoded-token-values -- Demo frame size. */
  block-size: 28rem;
  border: var(--semantic-dimension-border-width-default) solid
    var(--semantic-color-border-secondary);
}

.header {
  padding-inline: var(--semantic-dimension-space-padding-x-large);
  border-block-end: var(--semantic-dimension-border-width-small) solid
    var(--semantic-color-border-secondary);
}

.content {
  padding: var(--semantic-dimension-space-padding-x-large);
}
```

```tsx
<TopNavigation.Menu defaultPanel="main">
  <TopNavigation.MenuTrigger aria-label="Navigation menu" />
  <TopNavigation.MenuPopup aria-label="Navigation menu">
    <nav aria-label="Main">
      <TopNavigation.MenuPanel panel="main">
        <TopNavigation.MenuList>
          <TopNavigation.MenuItem>
            <TopNavigation.MenuPanelTrigger panel="docs" />
          </TopNavigation.MenuItem>
        </TopNavigation.MenuList>
      </TopNavigation.MenuPanel>
      <TopNavigation.MenuPanel panel="docs" parent="main" backLabel="Main menu">
        <TopNavigation.MenuGroup>
          <TopNavigation.MenuGroupLabel />
          <TopNavigation.MenuList>
            <TopNavigation.MenuItem>
              <TopNavigation.MenuCollapsible>
                <TopNavigation.MenuCollapsibleTrigger />
                <TopNavigation.MenuCollapsiblePanel>
                  <TopNavigation.MenuList />
                </TopNavigation.MenuCollapsiblePanel>
              </TopNavigation.MenuCollapsible>
            </TopNavigation.MenuItem>
          </TopNavigation.MenuList>
        </TopNavigation.MenuGroup>
      </TopNavigation.MenuPanel>
    </nav>
  </TopNavigation.MenuPopup>
</TopNavigation.Menu>
```

Panels hold the menu's levels. Each opening starts at `defaultPanel`; a `MenuPanelTrigger` slides to a nested panel, which starts with a back button and takes focus there. Panels slide with `semantic.transition.movement.disclosure`, instantly when the reader prefers reduced motion. One level of nesting. Within a panel, `MenuCollapsible` sections open in place, like [Side navigation](/components/side-navigation.md)'s, and `MenuGroup` gathers lists under a `MenuGroupLabel`, which names the group for assistive technology. Groups are optional, but every group needs a label.

Items are styled for the menu on their own, not shared with Side navigation. `active` marks the current page with `aria-current="page"`, or the panel trigger that contains it with `aria-current="true"`; a section that contains the current page is marked too. Render the menu and the wide navigation from the same data, and switch between them with your CSS at your breakpoint.

`MenuLink` renders an `a` and passes its props and ref through, so a router can wrap it. A router link that sets `aria-current="page"` on the active link, like TanStack Router's, marks the current page without `active`:

```tsx
const MenuLink = createLink(TopNavigation.MenuLink);

<MenuLink to="/docs/rollbacks">Rollbacks</MenuLink>;
```

### Static rendering

On pages rendered on the server without React in the browser, use the static menu parts; `MenuList` and `MenuItem` stay the same:

```tsx
<TopNavigation.StaticMenu defaultPanel="main">
  <TopNavigation.StaticMenuTrigger aria-label="Navigation menu" />
  <TopNavigation.StaticMenuPopup aria-label="Navigation menu">
    <nav aria-label="Main">
      <TopNavigation.StaticMenuPanel panel="main">
        <TopNavigation.MenuList>
          <TopNavigation.MenuItem>
            <TopNavigation.StaticMenuPanelTrigger panel="docs">
              Docs
            </TopNavigation.StaticMenuPanelTrigger>
          </TopNavigation.MenuItem>
        </TopNavigation.MenuList>
      </TopNavigation.StaticMenuPanel>
      <TopNavigation.StaticMenuPanel panel="docs" parent="main" backLabel="Main menu">
        <TopNavigation.StaticMenuCollapsible>
          <TopNavigation.StaticMenuCollapsibleTrigger>
            Guides
          </TopNavigation.StaticMenuCollapsibleTrigger>
          <TopNavigation.StaticMenuCollapsiblePanel>…</TopNavigation.StaticMenuCollapsiblePanel>
        </TopNavigation.StaticMenuCollapsible>
      </TopNavigation.StaticMenuPanel>
    </nav>
  </TopNavigation.StaticMenuPopup>
</TopNavigation.StaticMenu>
```

```ts
import {createCollapsible, createTopNavigationMenu, enhance} from '@earendil-works/elanor-react';

enhance({'top-navigation-menu': createTopNavigationMenu, collapsible: createCollapsible});
```

The popup is a native popover. The behavior opens and closes it like the React menu: Escape and choosing a link close it, presses outside leave it open, panels slide with the same focus moves, and each opening starts at `defaultPanel`. Without the behavior, the trigger still opens and closes it.

## Responsive navigation

On desktop, destinations are in a [Sidebar](/components/sidebar.md) and resources in the header. Below `semantic.dimension.breakpoint.medium`, both move into a [Drawer](/components/drawer.md). They share the current selection; the sidebar's collapsed state stays separate from the drawer's open state.

The drawer closes when the window grows past the breakpoint, rather than being hidden by CSS. Try it: open the drawer on a narrow window, then widen it. The demo keeps selection in local state and opens the drawer inside its frame with Portal's `container`; an application uses its router and lets the drawer cover the viewport.

```tsx
// responsive.tsx
import {CloseIcon, MenuIcon} from '@earendil-works/elanor-icons-react';
import {
  Drawer,
  Heading,
  IconButton,
  Layout,
  Link,
  SideNavigation,
  Sidebar,
  Stack,
  Text,
  TopNavigation,
  type DrawerRootActions,
} from '@earendil-works/elanor-react';
import {useEffect, useRef, useState} from 'react';

import styles from './responsive.module.css';

const pages = ['Overview', 'Projects', 'Activity', 'Members', 'Usage', 'Billing', 'Settings'];

function Destinations({
  active,
  collapsed = false,
  onSelect,
}: {
  readonly active: string;
  readonly collapsed?: boolean;
  readonly onSelect: (page: string) => void;
}) {
  return (
    <SideNavigation.Root aria-label="Workspace" variant={collapsed ? 'compact' : 'default'}>
      <SideNavigation.List>
        {pages.map((page) => (
          <SideNavigation.Item key={page}>
            <SideNavigation.Link
              href="#"
              active={active === page}
              compactContent={page.slice(0, 2)}
              onClick={(event) => {
                // This demo selects local content; real applications supply their router links.
                event.preventDefault();
                onSelect(page);
              }}
            >
              {page}
            </SideNavigation.Link>
          </SideNavigation.Item>
        ))}
      </SideNavigation.List>
    </SideNavigation.Root>
  );
}

function Resources({onNavigate}: {readonly onNavigate?: () => void}) {
  return (
    <nav aria-label="Resources" className={styles.links}>
      <Link href="#" onClick={onNavigate}>
        Docs
      </Link>
      <Link href="#" onClick={onNavigate}>
        Support
      </Link>
    </nav>
  );
}

export default function TopNavigationResponsive() {
  const [active, setActive] = useState('Overview');
  const [collapsed, setCollapsed] = useState(false);
  const actionsRef = useRef<DrawerRootActions>(null);
  const triggerRef = useRef<HTMLButtonElement>(null);
  const contentRef = useRef<HTMLElement>(null);
  // Only for this demo: the drawer opens inside the application frame, not over the docs page.
  const applicationRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // CSS owns presentation. Only release the modal when its trigger becomes unavailable.
    const breakpoint = getComputedStyle(document.documentElement)
      .getPropertyValue('--semantic-dimension-breakpoint-medium')
      .trim();
    const desktop = window.matchMedia(`(min-width: ${breakpoint})`);
    const close = () => actionsRef.current?.close();
    const onBreakpointChange = () => {
      if (desktop.matches) close();
    };
    desktop.addEventListener('change', onBreakpointChange);
    return () => desktop.removeEventListener('change', onBreakpointChange);
  }, []);

  return (
    <Drawer.Root actionsRef={actionsRef} swipeDirection="left">
      <Layout.Root
        ref={applicationRef}
        render={<section aria-label="Acme workspace" />}
        className={styles.application}
      >
        <Layout.Header render={<TopNavigation.Root />} className={styles.header}>
          <TopNavigation.Identity>
            <Link href="#">Acme workspace</Link>
          </TopNavigation.Identity>
          <TopNavigation.Navigation className={styles.desktopOnly}>
            <Resources />
          </TopNavigation.Navigation>
          <TopNavigation.Actions className={styles.mobileOnly}>
            <Drawer.Trigger
              ref={triggerRef}
              render={<IconButton aria-label="Open workspace navigation" />}
            >
              <MenuIcon />
            </Drawer.Trigger>
          </TopNavigation.Actions>
        </Layout.Header>
        <Layout.Body className={styles.body}>
          <Sidebar.Root
            collapsed={collapsed}
            onCollapsedChange={setCollapsed}
            render={<Layout.Aside aria-label="Workspace sidebar" />}
            className={`${styles.desktopOnly} ${styles.sidebar}`}
          >
            <Sidebar.Header>
              <Sidebar.Trigger />
            </Sidebar.Header>
            <Sidebar.Content>
              <Destinations active={active} collapsed={collapsed} onSelect={setActive} />
            </Sidebar.Content>
          </Sidebar.Root>
          <Layout.Main render={<div />} ref={contentRef} tabIndex={-1} className={styles.content}>
            <Heading level={3}>{active}</Heading>
            <Text>Select a destination, or resize the window with the drawer open.</Text>
          </Layout.Main>
        </Layout.Body>
      </Layout.Root>
      <Drawer.Portal container={applicationRef} className={styles.overlay}>
        <Drawer.Backdrop />
        <Drawer.Viewport>
          <Drawer.Popup
            finalFocus={() =>
              triggerRef.current?.getClientRects().length ? triggerRef.current : contentRef.current
            }
          >
            <Drawer.Content>
              <Stack align="stretch" gap="large">
                <Stack direction="row" align="center" gap="auto">
                  <Drawer.Title>Workspace navigation</Drawer.Title>
                  <Drawer.Close render={<IconButton aria-label="Close workspace navigation" />}>
                    <CloseIcon />
                  </Drawer.Close>
                </Stack>
                <Destinations
                  active={active}
                  onSelect={(page) => {
                    setActive(page);
                    actionsRef.current?.close();
                  }}
                />
                <Resources onNavigate={() => actionsRef.current?.close()} />
              </Stack>
            </Drawer.Content>
          </Drawer.Popup>
        </Drawer.Viewport>
      </Drawer.Portal>
    </Drawer.Root>
  );
}
```

```css
/* responsive.module.css */
.application {
  /* Demo only: fixed-position drawer parts are placed and clipped within the frame. */
  contain: layout paint;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  /* stylelint-disable-next-line design-tokens/no-hardcoded-token-values -- Demo frame size. */
  block-size: 30rem;
  min-block-size: 0;
  border: var(--semantic-dimension-border-width-default) solid
    var(--semantic-color-border-secondary);
}

.header {
  padding: var(--semantic-dimension-space-padding-large);
  border-block-end: var(--semantic-dimension-border-width-default) solid
    var(--semantic-color-border-secondary);
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--semantic-dimension-space-gap-medium);
}

.body {
  min-block-size: 0;
}

.content {
  overflow: auto;
  padding: var(--semantic-dimension-space-padding-x-large);
}

/* Anchor the Drawer to the viewport above the application. */
.overlay {
  position: fixed;
  inset: 0;
  z-index: var(--semantic-number-layer-overlay);
}

.desktopOnly {
  display: none;
}

/* Media queries can't use var(), so the threshold is the token's resolved value. */
@media (width >= 64rem /* token: semantic.dimension.breakpoint.medium */) {
  .desktopOnly {
    display: flex;
  }

  .sidebar {
    position: static;
    block-size: 100%;
    max-block-size: 100%;
  }

  .mobileOnly {
    display: none;
  }
}
```

## API

### `TopNavigation.Root`

Renders `<header>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.Identity`

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.Navigation`

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.Link`

A link in the bar's navigation.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` | Marks the current page or section with `aria-current="page"`. Router links that set `aria-current` themselves need no `active`. |

Renders `<a>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.Actions`

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.ActionButton`

An icon button in the bar, styled like the menu's trigger. Name it with `aria-label`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `'submit' \| 'reset' \| 'button'` | `'button'` |  |

Also accepts Base UI [`Button`](https://base-ui.com/react/components/button.md) props.

### `TopNavigation.Menu`

The top navigation's menu for narrow screens: a `MenuTrigger` in the bar opens a `MenuPopup` below it, with `MenuPanel`s that slide to nested sections. Built on Base UI's Dialog, without modality: the bar's other controls stay usable, and clicking them leaves the menu open. Renders no element.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultPanel` (required) | `string` | - | The panel shown each time the menu opens. |

Also accepts Base UI [`Dialog.Root`](https://base-ui.com/react/components/dialog.md) props.

### `TopNavigation.MenuTrigger`

The menu button: the menu icon, which turns into the close icon while the menu is open.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` | - | The accessible name, such as "Navigation menu". |

Also accepts Base UI [`Dialog.Trigger`](https://base-ui.com/react/components/dialog.md) props.

### `TopNavigation.MenuPopup`

The open menu: it fills the window below the top navigation (`size.topNavigation`) and holds the panels, which share its space and slide sideways.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` | - | The accessible name, such as "Navigation menu". |
| `container` | `HTMLElement \| ShadowRoot \| RefObject<HTMLElement \| ShadowRoot \| null>` | - | Where the menu renders; the document body by default. |
| `children` | `ReactNode` | - | A `nav` with the panels, named like the navigation it replaces. |

Also accepts Base UI [`Dialog.Popup`](https://base-ui.com/react/components/dialog.md) props.

### `TopNavigation.MenuPanel`

A panel of items. One level of nesting.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `parent` | `never` | - |  |
| `backLabel` | `never` | - |  |
| `panel` (required) | `string` | - | Identifies the panel for `MenuPanelTrigger`s and the menu's `defaultPanel`. |

Renders `<div>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.MenuList`

Renders `<ul>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.MenuItem`

Renders `<li>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.MenuPanelTrigger`

An item that slides to a nested panel, marked with a chevron.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `panel` (required) | `string` | - | The nested panel this item opens. |
| `active` | `boolean` | `false` | Marks the item as the section with the current page (`aria-current="true"`). |

Renders `<button>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.MenuGroup`

Groups lists in a panel under a `MenuGroupLabel`, which names the group. Every group needs exactly one label.

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.MenuGroupLabel`

Renders `<div>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.MenuCollapsible`

A section of links in a panel that opens and closes in place. Put a `MenuCollapsibleTrigger` and a `MenuCollapsiblePanel` with a `MenuList` inside, in a `MenuItem`.

Accepts Base UI [`Collapsible.Root`](https://base-ui.com/react/components/collapsible.md) props.

### `TopNavigation.MenuCollapsibleTrigger`

The section's title, styled like a menu item with a chevron at the end.

Accepts Base UI [`Collapsible`](https://base-ui.com/react/components/collapsible.md) props.

### `TopNavigation.MenuCollapsiblePanel`

The section's links. It grows and shrinks to its measured height. Closed panels stay in the document by default (`keepMounted`), so their links are part of the page's HTML.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `keepMounted` | `boolean` | `true` | Whether to keep the element in the DOM while the panel is hidden. |

Also accepts Base UI [`Collapsible.Panel`](https://base-ui.com/react/components/collapsible.md) props.

### `TopNavigation.StaticMenu`

The top navigation's menu for pages without React in the browser; `createTopNavigationMenu` makes it work. Renders no element.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultPanel` (required) | `string` | - | The panel shown each time the menu opens. |
| `id` | `string` | - | The menu's id, so other buttons can open it with `popovertarget`. Generated by default. |
| `children` | `ReactNode` | - |  |

Also accepts HTML attributes and `ref`.

### `TopNavigation.StaticMenuTrigger`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` (required) | `string` | - | The accessible name, such as "Navigation menu". |

Renders `<button>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.StaticMenuPopup`

The open menu below the top navigation; a native popover.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data-elanor` | `string` | - | The page's own behaviors for this element, attached after the component's. |
| `aria-label` (required) | `string` | - | The accessible name, such as "Navigation menu". |
| `children` | `ReactNode` | - | A `nav` with the panels, named like the navigation it replaces. |

Renders `<div>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.StaticMenuPanel`

A panel of items. One level of nesting.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `parent` | `never` | - |  |
| `backLabel` | `never` | - |  |
| `panel` (required) | `string` | - | Identifies the panel for `MenuPanelTrigger`s and the menu's `defaultPanel`. |

Renders `<div>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.StaticMenuPanelTrigger`

An item that slides to a nested panel, marked with a chevron.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `panel` (required) | `string` | - | The nested panel this item opens. |
| `active` | `boolean` | `false` | Marks the item as the section with the current page (`aria-current="true"`). |

Renders `<button>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.StaticMenuCollapsible`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data-elanor` | `string` | - | The page's own behaviors for this element, attached after the component's. |

Also accepts Base UI [`Collapsible.Root`](https://base-ui.com/react/components/collapsible.md) props.

### `TopNavigation.StaticMenuCollapsibleTrigger`

Renders `<button>`. Accepts its HTML attributes and `ref`.

### `TopNavigation.StaticMenuCollapsiblePanel`

Accepts Base UI [`Collapsible.Panel`](https://base-ui.com/react/components/collapsible.md) props.

### `TopNavigation.MenuLink`

A link in the menu. Choosing it closes the menu.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` | Marks the current page with `aria-current="page"`. Router links that set `aria-current` themselves need no `active`. |

Renders `<a>`. Also accepts its HTML attributes and `ref`.

### `TopNavigation.StaticMenuLink`

A link in the menu. Choosing it closes the menu.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` | Marks the current page with `aria-current="page"`. |

Renders `<a>`. Also accepts its HTML attributes and `ref`.
