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.
Code
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>
);
} .links {
display: flex;
flex-wrap: wrap;
gap: var(--semantic-dimension-space-gap-medium);
} Usage
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 divs. Put links inside a nav with an aria-label, or use Navigation menu 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, 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 or a menu, as in 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.
.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.
Code
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>
);
} .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.
Code
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>
);
} .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.
Open the menu, then open Docs.
Code
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>
);
} .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);
} <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’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:
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:
<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>
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 and resources in the header. Below semantic.dimension.breakpoint.medium, both move into a Drawer. 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.
Overview
Select a destination, or resize the window with the drawer open.
Code
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>
);
} .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 props.
TopNavigation.Menu
The top navigation's menu for narrow screens: a MenuTrigger in the bar opens a MenuPopup below it, with MenuPanels 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 |
|---|---|---|---|
defaultPanelrequired | string | - | The panel shown each time the menu opens. |
Also accepts Base UI Dialog.Root 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-labelrequired | string | - | The accessible name, such as "Navigation menu". |
Also accepts Base UI Dialog.Trigger 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-labelrequired | 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 props.
TopNavigation.MenuPanel
A panel of items. One level of nesting.
| Prop | Type | Default | Description |
|---|---|---|---|
parent | never | - | |
backLabel | never | - | |
panelrequired | string | - | Identifies the panel for MenuPanelTriggers 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 |
|---|---|---|---|
panelrequired | 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 props.
TopNavigation.MenuCollapsibleTrigger
The section's title, styled like a menu item with a chevron at the end.
Accepts Base UI Collapsible 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 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 |
|---|---|---|---|
defaultPanelrequired | 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-labelrequired | 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-labelrequired | 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 | - | |
panelrequired | string | - | Identifies the panel for MenuPanelTriggers 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 |
|---|---|---|---|
panelrequired | 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 props.
TopNavigation.StaticMenuCollapsibleTrigger
Renders <button>. Accepts its HTML attributes and ref.
TopNavigation.StaticMenuCollapsiblePanel
Accepts Base UI Collapsible.Panel 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.