The navigation menu component provides a horizontal or vertical navigation system for web applications. It supports both simple navigation links and complex dropdown content areas with unlimited nesting depth, making it ideal for main navigation bars, mega menus, and hierarchical navigation structures. The component features intelligent hover behavior, keyboard navigation, and automatic active state management.
Mega menus are preferred over multi-level nested menus for better user experience. Mega menus provide all options at once, reducing navigation depth and making it easier for users to find what they're looking for. Use multi-level nesting only when absolutely necessary for complex hierarchical structures.
✅ Use navigation menu components when:
- Creating main site navigation bars
- Implementing mega menus with rich content
- Organizing hierarchical navigation structures with multiple levels
- Building vertical side navigation menus
- Creating navigation with hover-activated dropdowns
❌ Don't use navigation menu components for:
- Simple action menus (use command components)
- Form selection dropdowns (use select or combobox)
- Contextual actions on content (use command components)
- Breadcrumb navigation (use breadcrumb component)
A simple horizontal navigation bar with active state management and clean styling.
Navigation menu items support a simplified API using properties directly on the component. When href is provided, the default trigger automatically renders as a link. Use the label attribute to set the trigger text without needing a slot.
Navigation menu items support standard link properties (href, target, rel) that are passed to the default trigger. This is especially useful for external links or when you need to control link behavior.
When using the navigation menu in React applications with Next.js, you can use the slot="link" pattern to integrate Next.js <Link> component for SPA routing. This allows Next.js to handle client-side navigation while maintaining the navigation menu's styling and behavior.
import Link from 'next/link';
import { DapDSNavigationMenu, DapDSNavigationMenuItem } from 'dap-design-system/react';
const navigationItems = [
{ href: '/', label: 'Home' },
{ href: '/about', label: 'About' },
{ href: '/services', label: 'Services' },
{ href: '/contact', label: 'Contact' },
];
function Navigation() {
return (
<DapDSNavigationMenu activeHref="/about">
{navigationItems.map((item) => (
<DapDSNavigationMenuItem
key={item.href}
label={item.label}
>
<Link slot="link" href={item.href}>
{item.label}
</Link>
</DapDSNavigationMenuItem>
))}
</DapDSNavigationMenu>
);
}
Note: The slot="link" pattern replaces the default anchor element that would normally be rendered when using the href property. This allows you to use custom link components like Next.js <Link> while maintaining all the navigation menu's functionality, including active state management and keyboard navigation.
Navigation items can contain rich dropdown content for complex menu structures. You can use either the label attribute or the title slot to set the trigger text.
Note: When using the label attribute, if you provide custom content in the title slot, the slot content takes precedence over the attribute value. This allows you to use the simpler attribute API by default while still supporting custom slot content when needed.
Create complex mega menus with grid layouts and organized content sections. Use the fullWidth attribute to make the menu take full width of the screen.
Navigation menus support unlimited nesting depth with automatic placement and keyboard navigation. The label attribute provides a simpler way to set trigger text for nested items.
Navigation items support custom trigger elements beyond standard buttons.
Customize the expand indicator icon using the indicator slot.
Implementation of a complete site navigation with branding and user actions.
The navigation menu component can be customized using CSS custom properties for layout spacing and popup appearance.
.custom-navigation {
/* Increase spacing between navigation items */
--dds-navigation-menu-item-gap: var(--dds-spacing-300);
}
.custom-navigation-item {
/* Customize dropdown appearance */
--dds-popup-background: var(--dds-background-neutral-subtle);
--dds-popup-border-radius: var(--dds-radius-large);
--dds-popup-border-color: red;
--dds-popup-border-width: 2px;
--dds-popup-padding: var(--dds-spacing-400);
}
.custom-navigation-item dap-ds-button[variant="subtle-menu"] {
/* Custom button styling for menu items */
--dds-button-padding-x: var(--dds-spacing-400);
--dds-button-padding-y: var(--dds-spacing-300);
--dds-button-border-radius: var(--dds-radius-large);
/* Hover and active states */
--dds-button-subtle-background-neutral-hover: var(--dds-indigo-600);
--dds-button-subtle-background-neutral-pressed: var(--dds-indigo-700);
--dds-button-subtle-text-neutral-hover: var(--dds-button-primary-text-enabled);
}
.custom-navigation-item dap-ds-button[variant="subtle-menu-item"] {
/* Custom styling for dropdown menu items */
--dds-button-subtle-menu-item-padding: var(--dds-spacing-300);
--dds-button-subtle-menu-item-border-radius: var(--dds-radius-base);
--dds-button-subtle-menu-item-color-bg-hover: var(--dds-indigo-600);
}
- Full keyboard navigation with Arrow keys, Tab, Enter, and Escape
- Screen reader support with proper ARIA roles and labels
- Focus management with automatic dropdown closure
- Active state announcements for current page/section
- Tab: Navigate between top-level items
- ArrowLeft/ArrowRight (horizontal): Navigate between items
- ArrowUp/ArrowDown (vertical): Navigate between items
- ArrowDown/ArrowUp (horizontal): Open submenu if item has one
- ArrowRight (vertical): Open submenu if item has one
- Enter/Space: Activate current item (open submenu or navigate)
- Home/End: Jump to first/last item
- ArrowDown: Move to next sibling
- ArrowUp: Move to previous sibling
- ArrowRight: Open nested submenu or move into submenu
- ArrowLeft: Close parent submenu and return focus
- Escape: Close parent submenu
- Enter/Space: Activate link or open submenu
The component automatically provides:
- Semantic
<nav>element (no menu/menubar roles - this is navigation, not application menu) aria-current="page"for active navigation itemsaria-expandedstates for dropdown triggersaria-haspopupfor items with submenusaria-controlslinking triggers to their dropdowns- Screen readers announce as navigation landmark
- Mouse Hover: When hovering over a navigation item with a submenu, a 100ms delay timer starts. If the mouse stays, the submenu opens automatically.
- Sibling Management: When hovering over a different item, sibling items' submenus are automatically closed to ensure only one submenu is open at a time.
- Click After Hover: If a submenu was opened by hover and you click it, the submenu stays open (doesn't toggle closed). The hover flag is cleared so future clicks will toggle normally.
- Simple Links: Clicking navigates to the href
- Items with Submenus:
- First click opens the submenu
- Second click closes it (toggle behavior)
- If opened by hover first, clicking keeps it open
Items can be marked as active using different matching strategies:
- Exact Match:
href === activeHref(default) - Prefix Match: Use
baseHrefproperty for prefix matching - item will be active ifactiveHref.startsWith(baseHref) - Exact Only: Set
exactHref="true"to require exact match even whenbaseHrefis set
<!-- Prefix matching example -->
<dap-ds-navigation-menu activeHref="/products/web-dev">
<dap-ds-navigation-menu-item baseHref="/products">
<!-- This item will be active because /products/web-dev starts with /products -->
<span slot="title">Products</span>
</dap-ds-navigation-menu-item>
</dap-ds-navigation-menu>
Navigation menu items support standard HTML link properties that are passed to the default trigger when href is provided:
href(string): The URL for the navigation item. When provided, the default trigger renders as an anchor element instead of a button.target(string): The target attribute for link navigation. Valid values:_blank,_self,_parent,_top. Defaults to_self.rel(string): The rel attribute for link security and behavior. Useful for external links (e.g.,noopener noreferrer).
<!-- Simple link -->
<dap-ds-navigation-menu-item href="/about" label="About">
</dap-ds-navigation-menu-item>
<!-- External link with security attributes -->
<dap-ds-navigation-menu-item
href="https://example.com"
target="_blank"
rel="noopener noreferrer"
label="External Link">
</dap-ds-navigation-menu-item>
The label attribute provides a simplified way to set the trigger text without needing a slot. When provided, it displays text in the default title slot. If custom content is provided in the title slot, the slot content takes precedence.
<!-- Using label attribute (simpler) -->
<dap-ds-navigation-menu-item label="Products">
<!-- Dropdown content -->
</dap-ds-navigation-menu-item>
<!-- Using title slot (more flexible) -->
<dap-ds-navigation-menu-item>
<span slot="title">
<dap-ds-icon name="products-line"></dap-ds-icon>
Products
</span>
<!-- Dropdown content -->
</dap-ds-navigation-menu-item>
<!-- Slot content overrides label attribute -->
<dap-ds-navigation-menu-item label="Default Text">
<span slot="title">Custom Slot Content</span>
<!-- "Custom Slot Content" will be displayed, not "Default Text" -->
</dap-ds-navigation-menu-item>
You can combine all properties for a complete navigation item setup:
<dap-ds-navigation-menu-item
href="/products"
target="_self"
rel="noopener"
label="Products"
baseHref="/products"
activeHref="/products/web-dev">
<!-- Dropdown content -->
</dap-ds-navigation-menu-item>
The navigation menu component is built on top of the popup component, combining navigation semantics with positioning capabilities. It automatically manages:
- Active states based on href matching
- Dropdown interactions with hover delay and click behavior
- Keyboard navigation with full arrow key support
- Nested item management with automatic level and placement calculation
- Sibling coordination to ensure proper submenu behavior
- Positioning that escapes constrained containers using fixed positioning strategy
- Link properties passed to default trigger for seamless navigation
The component uses Floating UI for intelligent positioning and automatically adjusts placement based on nesting level and orientation. When href is provided, the default trigger automatically renders as an anchor element, making it a proper link for navigation and SEO purposes.
import { DapDSNavigationMenu } from 'dap-design-system'
import { DapDSNavigationMenuReact } from 'dap-design-system/react'
For optimal bundle sizes, use the tree-shakeable import syntax:
import { DapDSNavigationMenu } from 'dap-design-system/components'
| Property | Type | Default | Description |
|---|---|---|---|
activeHref | string | The currently active href for highlighting active navigation items with aria-current="page". Defaults to window.location.pathname if not provided. | |
orientation | 'horizontal', 'vertical' | 'horizontal' | The orientation of the navigation menu. Default is 'horizontal'. |
fullWidth | boolean | false | Whether the navigation menu should take full width of the screen. Default is false. |
allowMultipleOpen | boolean | true | Whether multiple dropdown branches can stay open simultaneously (accordion mode). Default is true. |
| Name | Description |
|---|---|
(default) | The navigation menu list and items. |
| Event Name | Description | Type |
|---|---|---|
dds-navigation-item-click | Fired when a navigation item is clicked. | {href: string, event: Event } |
| Part Name | Description |
|---|---|
base | The main navigation menu container (nav element). |
You can style CSS parts using the ::part() pseudo-element selector:
/* Target a specific part */
.my-custom-dap-ds-navigation-menu::part(base) {
/* Your custom styles */
}
Example usage:
<dap-ds-navigation-menu class="my-custom-dap-ds-navigation-menu">
Navigation menu
</dap-ds-navigation-menu>
.my-custom-dap-ds-navigation-menu::part(base) {
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
CSS parts allow you to style internal elements of the component while maintaining encapsulation. Learn more in our styling guide.
| Property Name | Description |
|---|---|
--dds-navigation-menu-item-gap | The gap between navigation menu items. (default: var(--dds-spacing-100)). |
CSS custom properties (CSS variables) can be set directly on the component or in your stylesheet:
Method 1: Inline styles (Quick customization)
<dap-ds-navigation-menu
style="--dds-navigation-menu-item-gap: value; ">
Navigation menu
</dap-ds-navigation-menu>
Method 2: CSS classes (Reusable styles)
.my-custom-dap-ds-navigation-menu {
--dds-navigation-menu-item-gap: value;
}
<dap-ds-navigation-menu class="my-custom-dap-ds-navigation-menu">
Navigation menu
</dap-ds-navigation-menu>
Method 3: Global theme customization
/* Apply to all instances */
dap-ds-navigation-menu {
--dds-navigation-menu-item-gap: value;
}
CSS custom properties inherit through the Shadow DOM, making them perfect for theming. Changes apply immediately without rebuilding.
| Property | Type | Default | Description |
|---|---|---|---|
disabled | boolean | false | The disabled state of the popup. Default is false. |
opened | boolean | false | The open state of the popup. Default is false. |
placement | 'top', 'right' , 'bottom' , 'left' , 'top-start' , 'top-end' , 'bottom-start' , 'bottom-end' , 'left-start' , 'left-end' , 'right-start' , 'right-end' | 'bottom-start' | The placement of the popup (automatically adjusted based on nesting level). Default is 'bottom-start'. |
floatingStrategy | 'absolute', 'fixed' | 'fixed' | The floating strategy of the popup. Default is 'fixed'. |
offset | number | 0 | The offset of the popup. Default is 0. |
sync | boolean | false | Whether the popup should sync its width with the trigger. Default is false. |
maxHeight | number, 'auto' | 250 | The maximum height of the popup. Default is 250. |
maxWidth | number, 'auto' | 'auto' | The maximum width of the popup. Default is 'auto'. |
hasArrow | boolean | false | Whether the popup has an arrow. Default is false. |
overflow | boolean | true | Whether the popup should overflow. Default is true. |
fullWidth | boolean | false | Whether the popup should take full width of the screen. Default is false. |
icon | string | The name of the icon to display in the trigger. | |
ariaLabelledBy | string | The name of the element that labels the navigation dropdown. | |
activeHref | string | The href of the navigation item that is active (receives aria-current="page"). | |
baseHref | string | The href of the navigation item. If provided, the navigation item will be active if the href is a substring of the activeHref. | |
exactHref | boolean | false | Whether the navigation item should be active if the href is exactly the same as the activeHref. Default is false. |
href | string | The href URL for the navigation item link. When provided, the default trigger renders as a link. | |
target | '_blank', '_self' , '_parent' , '_top' | The target attribute for link navigation. | |
rel | string | The rel attribute for link security and behavior. | |
label | string | The label text for the navigation item. This will be displayed in the title slot if no custom title slot content is provided. | |
level | number | 0 | The nesting level of this navigation item (0 = top-level, automatically set for nested items). Default is 0. |
orientation | 'horizontal', 'vertical' | 'horizontal' | The orientation of the parent navigation menu (automatically inherited). Default is 'horizontal'. |
hasContent | boolean | false | |
size | 'xs', 'sm' , 'lg' | The size of the popup. Default is 'sm'. |
| Name | Description |
|---|---|
trigger | The trigger element (link, button, etc.) for this navigation item. |
title | The title of the navigation item. |
indicator | The expand indicator icon (defaults to arrow icon based on orientation and level). |
link | A router link element (e.g. Next.js Link) used for SPA navigation. Rendered outside the popup so it is always accessible to programmatic clicks. |
(default) | The dropdown content (can contain nested dap-ds-navigation-menu-item elements). |
| Event Name | Description | Type |
|---|---|---|
dds-navigation-item-click | Fired when a navigation item is clicked. | {href: string, event: Event } |
dds-navigation-dropdown-open | Fired when a navigation dropdown is opened. | {item: DapDSNavigationMenuItem } |
| Part Name | Description |
|---|---|
base | The base part of the navigation item. |
trigger | The trigger element part. |
trigger-base | The base part of the trigger button (from dap-ds-button). |
trigger-content | The content part of the trigger button (from dap-ds-button). |
trigger-high-contrast | The high contrast part of the trigger button (from dap-ds-button). |
trigger-stack-base | The base part of the trigger stack (from dap-ds-stack). |
title | The title slot wrapper. |
indicator | The expand indicator container. |
indicator-icon | The expand indicator icon part. |
indicator-icon-base | The base part of the indicator icon (from dap-ds-icon). |
indicator-icon-icon | The icon part of the indicator icon (from dap-ds-icon). |
popup | The dropdown popup container. |
content | The dropdown content wrapper (default slot). |
arrow | The dropdown arrow part. |
| Property Name | Description |
|---|---|
--dds-navigation-menu-item-spacing | The padding/spacing of the navigation menu item content. (default: var(--dds-spacing-200)). |
--dds-navigation-menu-item-border-width | The border width of the navigation menu item content. (default: var(--dds-border-width-base)). |
--dds-navigation-menu-item-border-color | The border color of the navigation menu item content. (default: var(--dds-border-neutral-subtle)). |
--dds-navigation-menu-item-border-radius | The border radius of the navigation menu item content. (default: var(--dds-radius-base)). |
--dds-navigation-menu-item-background | The background color of the navigation menu item content. (default: var(--dds-background-neutral-base)). |
--dds-navigation-menu-item-shadow | The box shadow of the navigation menu item content. (default: var(--dds-shadow-base)). |