For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/llms-full.txt, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/components/menu.md.

Menu 菜单

按层级组织页面导航。

基本用法

通过 itemRender 在菜单文字后追加状态标签和数字标签。一级菜单保留图标,二级菜单仅展示文字,并与一级菜单文字对齐。子菜单采用更紧凑的行高与较淡的文字;可选条目悬停和按下时仅改变文字颜色,选中项保留底色,祖先菜单保持普通样式。

basic.tsx
import { Menu, Tag } from '@ifhover/leaf-ui';

import { FolderKanban, House, Settings, Users } from 'lucide-react';

export function MenuBasic() {
  return (
    <Menu
      mode="inline"
      defaultSelectedKey="design"
      defaultOpenKeys={['projects']}
      itemRender={(item, content) => (
        <>
          {content}
          {item.key === 'release' && (
            <Tag size="sm" color="primary">
              新
            </Tag>
          )}
          {item.key === 'team' && (
            <Tag size="sm" color="info">
              12
            </Tag>
          )}
        </>
      )}
      items={[
        { key: 'home', label: '工作台', icon: <House /> },
        {
          key: 'projects',
          label: '项目管理',
          icon: <FolderKanban />,
          children: [
            { key: 'design', label: '设计资源' },
            { key: 'release', label: '发布管理' },
          ],
        },
        { key: 'team', label: '团队成员', icon: <Users /> },
        { key: 'settings', label: '设置', icon: <Settings /> },
      ]}
    />
  );
}

排列方式

通过 Tabs 切换垂直、内嵌、横向和折叠侧栏。垂直与内嵌示例默认展开子菜单;横向导航和折叠侧栏使用浮层展示二级菜单。折叠侧栏还可以通过按钮展开,比较两种状态。

advanced.tsx
import type { MenuItem } from '@ifhover/leaf-ui';

import { Button, Flex, Menu, Tabs } from '@ifhover/leaf-ui';

import { Folder, House, Settings } from 'lucide-react';

import { useState } from 'react';

const items: MenuItem[] = [
  { key: 'home', label: '概览', icon: <House /> },
  {
    key: 'resources',
    label: '资源',
    icon: <Folder />,
    children: [
      { key: 'components', label: '组件' },
      { key: 'themes', label: '主题' },
    ],
  },
  { key: 'settings', label: '设置', icon: <Settings /> },
];

export function MenuLayouts() {
  const [collapsed, setCollapsed] = useState(true);
  return (
    <Tabs
      aria-label="菜单排列方式"
      items={[
        {
          key: 'vertical',
          label: '垂直',
          children: (
            <Menu
              mode="vertical"
              items={items}
              defaultSelectedKey="components"
              defaultOpenKeys={['resources']}
            />
          ),
        },
        {
          key: 'inline',
          label: '内嵌',
          children: (
            <Menu
              mode="inline"
              items={items}
              defaultSelectedKey="components"
              defaultOpenKeys={['resources']}
            />
          ),
        },
        {
          key: 'horizontal',
          label: '横向',
          children: <Menu mode="horizontal" items={items} defaultSelectedKey="components" />,
        },
        {
          key: 'collapsed',
          label: '折叠侧栏',
          children: (
            <Flex direction="column" gap="md" align="start">
              <Button
                variant="soft"
                size="sm"
                aria-expanded={!collapsed}
                onClick={() => setCollapsed((value) => !value)}
              >
                {collapsed ? '展开侧栏' : '折叠侧栏'}
              </Button>
              <Menu collapsed={collapsed} items={items} defaultSelectedKey="components" />
            </Flex>
          ),
        },
      ]}
    />
  );
}

垂直菜单分组

在 items 中使用 type: 'group',通过 label 设置分组标题、children 设置菜单项。标题支持 ReactNode,不参与选择和方向键导航;折叠菜单隐藏标题,保留菜单项。

groups.tsx
import { Menu } from '@ifhover/leaf-ui';

export function MenuGroups() {
  return (
    <Menu
      defaultSelectedKey="overview"
      items={[
        {
          key: 'workspace',
          type: 'group',
          label: '工作空间',
          children: [
            { key: 'overview', label: '概览' },
            { key: 'projects', label: '项目' },
          ],
        },
        {
          key: 'account',
          type: 'group',
          label: '账户',
          children: [{ key: 'settings', label: '设置' }],
        },
      ]}
    />
  );
}

复用分组标题

MenuGroup 复用菜单的分组标题样式,title 接受 ReactNode,children 可以放菜单、说明、按钮或任意自定义元素。只有菜单项时优先使用上面的 items 分组,共享同一个 Menu 的选择状态与键盘导航。

自定义内容可以作为 Menu 的相邻区域放进独立的 MenuGroup,控件保留自己的 Tab 顺序和键盘行为。MenuGroup 自身不管理选中项、展开或折叠;侧栏收起时,由应用决定是否隐藏这个区域。

快捷操作
分组标题下可以放说明、按钮和其他自定义内容。
custom-group.tsx
import { Button, MenuGroup, Space, Text } from '@ifhover/leaf-ui';

import { useState } from 'react';

export function MenuCustomGroup() {
  const [created, setCreated] = useState(false);
  return (
    <MenuGroup title="快捷操作">
      <Space direction="vertical" align="start">
        <Text tone="muted">分组标题下可以放说明、按钮和其他自定义内容。</Text>
        <Button onClick={() => setCreated(true)}>创建项目</Button>
        {created && <Text role="status">项目已创建</Text>}
      </Space>
    </MenuGroup>
  );
}

受控导航与路由

selectedKey 跟随当前路由。linkRender 可替换默认链接,例如 React Router 或 Next.js 的 Link;保留传入链接的事件、类名和 ARIA 属性。

当前页面:overview。示例切换本地状态;接入路由时使用自己的 Link,并保留传入属性。
capabilities.tsx
import type { MenuItem } from '@ifhover/leaf-ui';

import { Menu, Space, Text } from '@ifhover/leaf-ui';

import { BookOpen, House } from 'lucide-react';

import { type AnchorHTMLAttributes, cloneElement, isValidElement, type ReactElement, useState } from 'react';

export function MenuRouting() {
  const [selected, setSelected] = useState('overview');
  const items: MenuItem[] = [
    { key: 'overview', label: '概览', href: '#overview', icon: <House /> },
    {
      key: 'resources',
      label: '资源',
      icon: <BookOpen />,
      children: [
        { key: 'guides', label: '使用指南', href: '#guides' },
        {
          key: 'settings',
          label: '设置',
          href: '#settings',
        },
      ],
    },
  ];
  return (
    <Space direction="vertical" align="center">
      <Menu
        aria-label={'应用导航'}
        items={items}
        selectedKey={selected}
        defaultOpenKeys={['resources']}
        onSelect={setSelected}
        linkRender={(_item, link) => {
          if (!isValidElement(link)) return link;
          const element = link as ReactElement<AnchorHTMLAttributes<HTMLAnchorElement>>;
          return cloneElement(element, {
            onClick: (event) => {
              element.props.onClick?.(event);
              event.preventDefault();
            },
          });
        }}
      />
      <Text tone="muted">
        {`当前页面:${selected}。示例切换本地状态;接入路由时使用自己的 Link,并保留传入属性。`}
      </Text>
    </Space>
  );
}

使用说明

纵向菜单默认 240px,折叠时 60px,可通过 style.width 调整。items 支持嵌套、分组、分隔线与链接。selectedKey 管理当前选中项,openKeys 管理展开项;横向模式与折叠模式使用浮层展示子菜单。

横向导航的子菜单在触发项下方展开;折叠侧栏的子菜单向侧边展开,并显示父级标题。浮层统一图标、文字、分组与选中背景,仅当前页面高亮,所属父项保持普通样式。RTL 布局自动反转侧向展开方向,靠近屏幕边缘时浮层会调整位置。

子菜单展开、收起以及侧栏宽度变化均带有过渡。收起的子项不会参与键盘导航;系统开启减少动态效果时,过渡会自动关闭。

菜单项、悬停背景和选中高亮与按钮共用基础圆角。通过 ConfigProvider 的 theme.borderRadius 统一调整,也可用 theme.components.Menu.borderRadius 单独设置菜单圆角。

可选条目悬停、键盘聚焦和按下时只改变文字与图标颜色,背景保持不变,选中项保留主题色底色;危险项使用危险色反馈。带子菜单的展开项继续使用浅色悬停背景和较深的按下背景。子菜单浮层沿用相同规则,子菜单默认行高为 32px,紧凑密度为 26px,并跟随区域控件高度调整。

类型

组装数据、定义回调和使用 ref 时,可以导入这些类型。

import type { MenuGroupProps, MenuItem, MenuProps } from '@ifhover/leaf-ui';
属性类型默认值说明
keystring必填必需。唯一且稳定的标识
labelReactNode必填必需。显示内容
iconReactNode—自定义图标
hrefstring—目标链接
disabledboolean—禁用交互。
dangerboolean—危险操作颜色
childrenreadonly MenuItem[]—内容或下级项目
type'item' | 'group' | 'divider'—显示或选项类型
属性类型默认值说明
itemsreadonly MenuItem[]必填必需。数据项目,保持 key 唯一且稳定
mode'vertical' | 'horizontal' | 'inline''vertical'菜单排列方式
selectedKeystring—受控选中项目
defaultSelectedKeystring''初始选中项目
onSelect(key: string, item: MenuItem) => void—选择项目
openKeysreadonly string[]—受控展开项目
defaultOpenKeysreadonly string[][]初始展开项目
onOpenChange(keys: string[]) => void—展开状态变化
collapsedbooleanfalse受控收起状态
itemRender(item: MenuItem, content: ReactNode) => ReactNode—自定义条目内容。
linkRender(item: MenuItem, link: ReactNode) => ReactNode—接入路由链接时保留传入的可访问性和事件属性。
itemClassNamestring | ((item: MenuItem) => string)—条目类名或按条目计算的类名。

支持原生 div 属性及指向 HTMLDivElement 的 ref。分组通过标题提供可访问名称,也可用 aria-label 或 aria-labelledby 覆盖。可从根入口或 @ifhover/leaf-ui/menu 导入。

属性类型默认值说明
titleReactNode必填必需。分组标题,与 Menu 内的分组共用样式
childrenReactNode—菜单或任意自定义内容