Menu
freeMenu gives sidebar and nested navigation a clear active route context.
motion
Preview
Dark
import { type ReactNode } from 'react';
import { PiBell, PiCreditCard, PiGear, PiUser, PiUsers } from 'react-icons/pi'
import Menu from '@/components/ui/Menu';
const MenuLabel = ({
icon,
children,
}: {
icon: ReactNode;
children: ReactNode;
}) => (
<span className="flex min-w-0 items-center gap-2">
{icon}
<span className="truncate">{children}</span>
</span>
);
export default function UsageDemo() {
return (
<div className="w-full max-w-[250px] rounded-card bg-card p-2">
<Menu
variant="subtle"
defaultActiveKeys={['profile']}
defaultExpandedKeys={['account']}
defaultCollapseActiveKeys={['account']}
>
<Menu.MenuItem eventKey="profile">
<MenuLabel icon={<PiUser className="text-base" />}>
Profile
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuItem eventKey="notifications">
<MenuLabel icon={<PiBell className="text-base" />}>
Notifications
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuCollapse
eventKey="account"
label={
<MenuLabel icon={<PiGear className="text-base" />}>
Account
</MenuLabel>
}
>
<Menu.MenuItem eventKey="billing">
<MenuLabel icon={<PiCreditCard className="text-base" />}>
Billing
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuItem eventKey="team">
<MenuLabel icon={<PiUsers className="text-base" />}>
Team
</MenuLabel>
</Menu.MenuItem>
</Menu.MenuCollapse>
</Menu>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add MenuExamples
Basic
Preview
Dark
import Menu from '@/components/ui/Menu';
export default function BasicDemo() {
return (
<div className="w-full max-w-[250px] rounded-card bg-card p-2">
<Menu>
<Menu.MenuItem eventKey="overview">Overview</Menu.MenuItem>
<Menu.MenuItem eventKey="customers">Customers</Menu.MenuItem>
<Menu.MenuItem eventKey="orders">Orders</Menu.MenuItem>
</Menu>
</div>
);
}Variant
Preview
Dark
import Menu from '@/components/ui/Menu';
const items = ['Profile', 'Billing', 'Team'];
export default function VariantDemo() {
return (
<div className="w-full max-w-[250px] space-y-4">
<div className="rounded-card bg-card p-2">
<Menu defaultActiveKeys={['Profile']}>
{items.map((item) => (
<Menu.MenuItem key={item} eventKey={item}>
{item}
</Menu.MenuItem>
))}
</Menu>
</div>
<div className="rounded-card bg-card p-2">
<Menu variant="subtle" defaultActiveKeys={['Profile']}>
{items.map((item) => (
<Menu.MenuItem key={item} eventKey={item}>
{item}
</Menu.MenuItem>
))}
</Menu>
</div>
</div>
);
}With Icons
Preview
Dark
import { type ReactNode } from 'react';
import { PiArchive, PiEnvelope, PiGear } from 'react-icons/pi'
import Menu from '@/components/ui/Menu';
const MenuLabel = ({
icon,
children,
}: {
icon: ReactNode;
children: ReactNode;
}) => (
<span className="flex items-center gap-2">
{icon}
{children}
</span>
);
export default function WithIconsDemo() {
return (
<div className="w-full max-w-[250px] rounded-card bg-card p-2">
<Menu variant="subtle" defaultActiveKeys={['inbox']}>
<Menu.MenuItem eventKey="settings">
<MenuLabel icon={<PiGear className="text-base" />}>
Settings
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuItem eventKey="inbox">
<MenuLabel icon={<PiEnvelope className="text-base" />}>
Inbox
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuItem eventKey="archive">
<MenuLabel icon={<PiArchive className="text-base" />}>
Archive
</MenuLabel>
</Menu.MenuItem>
</Menu>
</div>
);
}Collapsible
Preview
Dark
import { type ReactNode } from 'react';
import {
PiArchive,
PiEnvelope,
PiFolderOpen,
PiGear,
PiTray,
} from 'react-icons/pi'
import Menu from '@/components/ui/Menu';
const MenuLabel = ({
icon,
children,
}: {
icon: ReactNode;
children: ReactNode;
}) => (
<span className="flex items-center gap-2">
{icon}
{children}
</span>
);
export default function CollapsibleDemo() {
return (
<div className="w-full max-w-[250px] rounded-card bg-card p-2">
<Menu defaultExpandedKeys={['mail']}>
<Menu.MenuItem eventKey="settings">
<MenuLabel icon={<PiGear className="text-base" />}>
Settings
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuCollapse
eventKey="mail"
label={
<MenuLabel icon={<PiEnvelope className="text-base" />}>
Mail
</MenuLabel>
}
>
<Menu.MenuItem eventKey="inbox">
<MenuLabel icon={<PiTray className="text-base" />}>
Inbox
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuItem eventKey="archive">
<MenuLabel icon={<PiArchive className="text-base" />}>
Archive
</MenuLabel>
</Menu.MenuItem>
<Menu.MenuCollapse
eventKey="folders"
label={
<MenuLabel icon={<PiFolderOpen className="text-base" />}>
Folders
</MenuLabel>
}
>
<Menu.MenuItem eventKey="priority">Priority</Menu.MenuItem>
<Menu.MenuItem eventKey="follow-up">Follow-up</Menu.MenuItem>
</Menu.MenuCollapse>
</Menu.MenuCollapse>
</Menu>
</div>
);
}Groups
Preview
Dark
import Menu from '@/components/ui/Menu';
export default function GroupsDemo() {
return (
<div className="w-full max-w-[250px] rounded-card bg-card p-2">
<Menu variant="subtle" defaultActiveKeys={['members']}>
<Menu.MenuGroup label="Workspace">
<Menu.MenuItem eventKey="overview">Overview</Menu.MenuItem>
<Menu.MenuItem eventKey="settings">Settings</Menu.MenuItem>
</Menu.MenuGroup>
<Menu.MenuGroup label="Team">
<Menu.MenuItem eventKey="members">Members</Menu.MenuItem>
<Menu.MenuItem eventKey="roles">Roles</Menu.MenuItem>
</Menu.MenuGroup>
</Menu>
</div>
);
}Active And Expanded
Preview
Dark
import Menu from '@/components/ui/Menu';
export default function ActiveExpandedDemo() {
return (
<div className="w-full max-w-[250px] rounded-card bg-card p-2">
<Menu
defaultActiveKeys={['contacts', 'review']}
defaultExpandedKeys={['customers']}
defaultCollapseActiveKeys={['customers']}
>
<Menu.MenuItem eventKey="dashboard">Dashboard</Menu.MenuItem>
<Menu.MenuCollapse eventKey="customers" label="Customers">
<Menu.MenuItem eventKey="contacts">Contacts</Menu.MenuItem>
<Menu.MenuItem eventKey="segments">Segments</Menu.MenuItem>
</Menu.MenuCollapse>
<Menu.MenuItem eventKey="review" isActive>
Review Queue
</Menu.MenuItem>
</Menu>
</div>
);
}Disabled
Preview
Dark
import Menu from '@/components/ui/Menu';
export default function DisabledDemo() {
return (
<div className="w-full max-w-[250px] rounded-card bg-card p-2">
<Menu>
<Menu.MenuItem eventKey="profile">Profile</Menu.MenuItem>
<Menu.MenuItem disabled eventKey="billing">
Billing
</Menu.MenuItem>
<Menu.MenuItem eventKey="team">Team</Menu.MenuItem>
</Menu>
</div>
);
}API
Menu is a compound component: Menu provides shared active, expanded, height, and variant context; Menu.MenuItem, Menu.MenuCollapse, and Menu.MenuGroup render the list structure.
Menu
| Prop | Description | Type | Default |
|---|---|---|---|
children | Menu item, group, or collapse content rendered inside the root list. | ReactNode | - |
className | Class names applied to the root ul. | string | - |
collapsed | Switches menu context into collapsed mode for group labels and collapse indicators. | boolean | false |
defaultActiveKeys | Keys matched against descendant menu items to mark them active. | string[] | [] |
defaultCollapseActiveKeys | Keys matched against collapse rows to mark those rows active. | string[] | [] |
defaultExpandedKeys | Collapse keys opened when the menu renders or when the key list changes. | string[] | [] |
menuItemHeight | Shared item and collapse trigger height, in pixels. | number | 36 |
onSelect | Called when a menu item selects an eventKey. | MenuSelectHandler | - |
ref | Ref forwarded to the root ul. | Ref<HTMLUListElement> | - |
variant | Visual treatment for active and hover states. | MenuVariant | 'default' |
...nativeProps | Native props applied to the root ul. | NativeMenuProps | - |
Menu.MenuItem
| Prop | Description | Type | Default |
|---|---|---|---|
asElement | Element or component rendered for the item. | ElementType | 'li' |
children | Item label or custom content. | ReactNode | - |
className | Class names merged with the item state classes. | string | - |
disabled | Applies disabled styling and data-disabled to the item. | boolean | - |
eventKey | Selection key passed to onSelect and matched by active key props. | string | - |
hierarchyIndicator | Shows the hierarchy indicator line before the item content. | boolean | - |
indicatorClassName | Class names applied to the hierarchy indicator wrapper. | string | - |
indicatorLineClassName | Class names applied to the hierarchy indicator line. | string | - |
isActive | Forces the item into the active state independent of defaultActiveKeys. | boolean | - |
menuItemHeight | Item height. Falls back to Menu context height. | string | number | Menu menuItemHeight |
onSelect | Item-level select handler. Falls back to Menu context onSelect. | MenuSelectHandler | Menu onSelect |
ref | Ref forwarded to the rendered item element. | Ref<HTMLElement> | - |
...nativeProps | Native props applied to the rendered item element. | NativeMenuItemProps | - |
Menu.MenuCollapse
| Prop | Description | Type | Default |
|---|---|---|---|
active | Marks the collapse trigger row active. | boolean | - |
children | Nested menu items, groups, or collapse rows. | ReactNode | - |
className | Class names applied to the collapse trigger row. | string | - |
eventKey | Key matched by defaultExpandedKeys and defaultCollapseActiveKeys. | string | - |
expanded | Opens the collapse row by default. | boolean | false |
hierarchyIndicator | Shows the hierarchy indicator before the trigger label. | boolean | - |
indent | Indents the nested content list. | boolean | true |
label | Trigger row label. | string | ReactNode | null |
onToggle | Called when the trigger row changes expanded state. | MenuToggleHandler | - |
...nativeProps | Native props applied to the root li. | NativeMenuCollapseProps | - |
Menu.MenuGroup
| Prop | Description | Type | Default |
|---|---|---|---|
children | Menu items or collapse rows rendered inside the group list. | ReactNode | - |
className | Class names applied to the group wrapper. | string | - |
label | Group heading content. Required. | string | ReactNode | - |
...nativeProps | Native props applied to the group wrapper. | NativeMenuGroupProps | - |
Types
type MenuVariant = 'default' | 'subtle';
type MenuSelectHandler = (eventKey: string, e: MouseEvent) => void;
type MenuToggleHandler = (
expanded: boolean,
e: MouseEvent<HTMLDivElement>,
) => void;
type NativeMenuProps = ComponentProps<'ul'>;
type NativeMenuItemProps = ComponentProps<ElementType>;
type NativeMenuCollapseProps = ComponentProps<'li'>;
type NativeMenuGroupProps = ComponentProps<'div'>;