NavigationMenu
Always-rendered nested link list with current-page marking.
import { NavigationMenu } from '@elirobinson/react/components/organisms/NavigationMenu';Styles: @elirobinson/react/styles/organisms/NavigationMenu.css — already included when you import @elirobinson/react/styles.css.
Show code
import { NavigationMenu } from '@elirobinson/react/components/organisms/NavigationMenu';
export default function Basic() {
return (
<NavigationMenu
className="demo-col"
currentPath="/apps/kids-recipes"
items={[
{ label: 'Home', href: '/' },
{
label: 'Apps',
href: '/apps',
items: [
{ label: 'Kids Recipes', href: '/apps/kids-recipes' },
{ label: 'Interactive Maths', href: '/apps/interactive-maths' },
],
},
{ label: 'Coaching Guides', href: '/coaching-guides' },
{ label: 'About', href: '/about' },
]}
/>
);
}When to use it
Use NavigationMenu for a site's real navigation — every item renders as a genuine <a>, so
it works with middle-click, cmd-click, and JavaScript disabled. It supports one level of nested
links (a section with sub-pages), but the nested items are always rendered inline, not hidden
behind a hover or click. If you need a menu that opens and closes on demand, that's
DropdownMenu, not this.
Without a current page
currentPath is optional. When it's omitted, or when it matches nothing in items, no item is
marked current — none gets aria-current.
Show code
import { NavigationMenu } from '@elirobinson/react/components/organisms/NavigationMenu';
export default function NoCurrentPath() {
return (
<NavigationMenu
className="demo-col"
items={[
{ label: 'AI consulting', href: '/ai-consulting' },
{ label: 'Tech support', href: '/tech-support' },
{ label: 'Contact', href: '/contact' },
]}
/>
);
}Section labels
href is optional. Omit it when a group's title is a heading rather than a page —
"Foundations" above a list of foundation pages — and the item renders as an inert
<span> instead of an <a>: not focusable, not a navigation target, never marked
current. The label also names its nested list through aria-labelledby, so a screen
reader announces "Foundations, list, 7 items" on descending into the group.
Don't reach for the group's first child href to satisfy the type. That makes the header
match currentPath whenever the child is open, so the header renders as the current page
alongside it and a second aria-current="page" appears in the tree.
Show code
import { NavigationMenu } from '@elirobinson/react/components/organisms/NavigationMenu';
export default function SectionLabels() {
return (
<NavigationMenu
className="demo-col"
currentPath="/foundations/color"
items={[
{
label: 'Foundations',
items: [
{ label: 'Color', href: '/foundations/color' },
{ label: 'Typography', href: '/foundations/typography' },
],
},
{
label: 'Atoms',
items: [
{ label: 'Avatar', href: '/components/avatar' },
{ label: 'Button', href: '/components/button' },
],
},
]}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
itemsrequired | NavigationMenuItem[] | — | — |
currentPath | string | — | — |
Also accepts all HTMLAttributes<HTMLElement> props.
Accessibility
- Renders a
<nav>landmark wrapping a plain, always-rendered nested<ul>/<li>/<a>tree — not a disclosure widget. Nothing is collapsed, hidden, or toggled. - Every item with an
hrefis a real<a href>, soTab/Shift+Tabalready walk the entire tree, including nested items, in document order — zero custom key handling. An item without anhrefis a<span>label, so it's skipped byTab— there's nothing there to activate. - The item matching
currentPathgetsaria-current="page"; every other item has the attribute omitted entirely (not set to"false"). Nothing is marked current ifcurrentPathmatches nothing. - Deliberately has no
Escapehandling and noaria-expanded/aria-controlsanywhere — there's no toggleable submenu here to escape out of or expand. That's an explicit design choice, not an oversight: if you need a collapsible or flyout submenu, this isn't that component. - Nested items render inside their parent
<li>as their own<ul>, so a screen reader announces "list, N items" once at the top level and again on descending into a nested group, rather than one flattened list. - A nested list under a hrefless label is named after that label via
aria-labelledby, so the group announces as "Foundations, list, N items". A nested list under a link parent stays unnamed — the link is a destination, not a title for the list beneath it.
Do
- Pass real, working hrefs — this is a navigation list, not a menu of actions.
- Omit href on a group title that names its children but isn’t a page of its own.
- Keep currentPath in sync with the router so aria-current always matches what’s on screen.
- Nest one level deep in practice — the recursive items field supports more, but three-plus levels are hard to scan visually.
Don't
- Use it for a flyout or disclosure submenu that opens on click or hover — reach for DropdownMenu for that toggle behavior.
- Attach onClick handlers that prevent navigation — every link should be genuinely middle-click-able.
- Give a section header its first child’s href — the header then renders as current whenever that child is open.
- Expect aria-expanded or collapse behavior — nested items are always rendered and always in the tab order.