组件

菜单栏

一种在桌面应用程序中常见的视觉持久菜单,可快速访问一组一致的命令。

import * as React from "react";
import { Menubar } from "radix-ui";
import { CheckIcon, ChevronRightIcon, DotFilledIcon, } from "@radix-ui/react-icons";
import "./styles.css";
const RADIO_ITEMS = ["Andy", "Benoît", "Luis"];
const CHECK_ITEMS = ["Always Show Bookmarks Bar", "Always Show Full URLs"];
const MenubarDemo = () => {
const [checkedSelection, setCheckedSelection] = React.useState([
CHECK_ITEMS[1],
]);
const [radioSelection, setRadioSelection] = React.useState(RADIO_ITEMS[2]);
return (
<Menubar.Root className="MenubarRoot">
<Menubar.Menu>
<Menubar.Trigger className="MenubarTrigger">File</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className="MenubarContent" align="start" sideOffset={5} alignOffset={-3} >
<Menubar.Item className="MenubarItem">
New Tab <div className="RightSlot">⌘ T</div>
</Menubar.Item>
<Menubar.Item className="MenubarItem">
New Window <div className="RightSlot">⌘ N</div>
</Menubar.Item>
<Menubar.Item className="MenubarItem" disabled>
New Incognito Window
</Menubar.Item>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Sub>
<Menubar.SubTrigger className="MenubarSubTrigger">
Share
<div className="RightSlot">
<ChevronRightIcon />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent className="MenubarSubContent" alignOffset={-5} >
<Menubar.Item className="MenubarItem">
Email Link
</Menubar.Item>
<Menubar.Item className="MenubarItem">Messages</Menubar.Item>
<Menubar.Item className="MenubarItem">Notes</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem">
Print… <div className="RightSlot">⌘ P</div>
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger className="MenubarTrigger">Edit</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className="MenubarContent" align="start" sideOffset={5} alignOffset={-3} >
<Menubar.Item className="MenubarItem">
Undo <div className="RightSlot">⌘ Z</div>
</Menubar.Item>
<Menubar.Item className="MenubarItem">
Redo <div className="RightSlot">⇧ ⌘ Z</div>
</Menubar.Item>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Sub>
<Menubar.SubTrigger className="MenubarSubTrigger">
Find
<div className="RightSlot">
<ChevronRightIcon />
</div>
</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent className="MenubarSubContent" alignOffset={-5} >
<Menubar.Item className="MenubarItem">
Search the web…
</Menubar.Item>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem">Find…</Menubar.Item>
<Menubar.Item className="MenubarItem">Find Next</Menubar.Item>
<Menubar.Item className="MenubarItem">
Find Previous
</Menubar.Item>
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem">Cut</Menubar.Item>
<Menubar.Item className="MenubarItem">Copy</Menubar.Item>
<Menubar.Item className="MenubarItem">Paste</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger className="MenubarTrigger">View</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className="MenubarContent" align="start" sideOffset={5} alignOffset={-14} >
{CHECK_ITEMS.map((item) => (
<Menubar.CheckboxItem className="MenubarCheckboxItem inset" key={item} checked={checkedSelection.includes(item)} onCheckedChange={() => setCheckedSelection((current) => current.includes(item) ? current.filter((el) => el !== item) : current.concat(item), ) } >
<Menubar.ItemIndicator className="MenubarItemIndicator">
<CheckIcon />
</Menubar.ItemIndicator>
{item}
</Menubar.CheckboxItem>
))}
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem inset">
Reload <div className="RightSlot">⌘ R</div>
</Menubar.Item>
<Menubar.Item className="MenubarItem inset" disabled>
Force Reload <div className="RightSlot">⇧ ⌘ R</div>
</Menubar.Item>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem inset">
Toggle Fullscreen
</Menubar.Item>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem inset">
Hide Sidebar
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
<Menubar.Menu>
<Menubar.Trigger className="MenubarTrigger">Profiles</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className="MenubarContent" align="start" sideOffset={5} alignOffset={-14} >
<Menubar.RadioGroup value={radioSelection} onValueChange={setRadioSelection} >
{RADIO_ITEMS.map((item) => (
<Menubar.RadioItem className="MenubarRadioItem inset" key={item} value={item} >
<Menubar.ItemIndicator className="MenubarItemIndicator">
<DotFilledIcon />
</Menubar.ItemIndicator>
{item}
</Menubar.RadioItem>
))}
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem inset">Edit…</Menubar.Item>
<Menubar.Separator className="MenubarSeparator" />
<Menubar.Item className="MenubarItem inset">
Add Profile…
</Menubar.Item>
</Menubar.RadioGroup>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
};
export default MenubarDemo;

特性

    可以是受控的或非受控的。

    支持具有可配置阅读方向的子菜单。

    支持项目、标签、项目组。

    支持可勾选的项目(单选或多选)。

    自定义侧边、对齐方式、偏移量、碰撞处理。

    可选渲染指向箭头。

    焦点完全受管理。

    完整的键盘导航。

    预输入支持。

安装

从命令行安装组件。

npm install @radix-ui/react-menubar

结构

导入所有部件并将它们组装在一起。

import { Menubar } from "radix-ui";
export default () => (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger />
<Menubar.Portal>
<Menubar.Content>
<Menubar.Label />
<Menubar.Item />
<Menubar.Group>
<Menubar.Item />
</Menubar.Group>
<Menubar.CheckboxItem>
<Menubar.ItemIndicator />
</Menubar.CheckboxItem>
<Menubar.RadioGroup>
<Menubar.RadioItem>
<Menubar.ItemIndicator />
</Menubar.RadioItem>
</Menubar.RadioGroup>
<Menubar.Sub>
<Menubar.SubTrigger />
<Menubar.Portal>
<Menubar.SubContent />
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.Arrow />
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);

API 参考

根组件

包含菜单栏的所有部件。

属性类型默认值
asChild
布尔值
defaultValue
字符串
无默认值
value
字符串
无默认值
onValueChange
函数
无默认值
dir
枚举
无默认值
loop
布尔值

顶级菜单项,包含触发器和内容组合。

属性类型默认值
asChild
布尔值
value
字符串
无默认值

触发器

切换内容的按钮。默认情况下,Menubar.Content 将相对于触发器定位自身。

属性类型默认值
asChild
布尔值
数据属性
[data-state]"打开" |"关闭"
[data-highlighted]

高亮时显示

[data-disabled]

禁用时显示

Portal

使用时,将内容部分传送到 body 中。

属性类型默认值
forceMount
布尔值
无默认值
container
HTMLElement
document.body

内容

菜单打开时弹出的组件。

属性类型默认值
asChild
布尔值
loop
布尔值
onCloseAutoFocus
函数
无默认值
onEscapeKeyDown
函数
无默认值
onPointerDownOutside
函数
无默认值
onFocusOutside
函数
无默认值
onInteractOutside
函数
无默认值
forceMount
布尔值
无默认值
side
枚举
"底部"
sideOffset
数字
0
align
枚举
"居中"
alignOffset
数字
0
avoidCollisions
布尔值
collisionBoundary
Boundary
[]
collisionPadding
number | Padding
0
arrowPadding
数字
0
sticky
枚举
"部分"
hideWhenDetached
布尔值
数据属性
[data-state]"打开" |"关闭"
[data-side]"左" |"右" |"底部" |"顶部"
[data-align]"开始" |"结束" |"居中"
CSS 变量描述
--radix-menubar-content-transform-origin从内容和箭头位置/偏移量计算的 transform-origin
--radix-menubar-content-available-width触发器和边界边缘之间的剩余宽度
--radix-menubar-content-available-height触发器和边界边缘之间的剩余高度
--radix-menubar-trigger-width触发器的宽度
--radix-menubar-trigger-height触发器的高度

箭头

一个可选的箭头元素,用于在菜单栏菜单旁边渲染。这可以用于帮助在视觉上将触发器与 Menubar.Content 链接起来。必须在 Menubar.Content 内部渲染。

属性类型默认值
asChild
布尔值
宽度
数字
10
高度
数字
5

项目

包含菜单栏项目的组件。

属性类型默认值
asChild
布尔值
禁用
布尔值
无默认值
onSelect
函数
无默认值
文本值
字符串
无默认值
数据属性
[data-highlighted]

高亮时显示

[data-disabled]

禁用时显示

分组

用于将多个 Menubar.Item 分组。

属性类型默认值
asChild
布尔值

标签

用于渲染标签。它不会通过箭头键获得焦点。

属性类型默认值
asChild
布尔值

复选框项目

可以控制并像复选框一样渲染的项目。

属性类型默认值
asChild
布尔值
checked
布尔值 | '不确定'
无默认值
onCheckedChange
函数
无默认值
禁用
布尔值
无默认值
onSelect
函数
无默认值
文本值
字符串
无默认值
数据属性
[data-state]"已选中" |"未选中"
[data-highlighted]

高亮时显示

[data-disabled]

禁用时显示

单选框组

用于将多个 Menubar.RadioItem 分组。

属性类型默认值
asChild
布尔值
value
字符串
无默认值
onValueChange
函数
无默认值

单选框项目

可以控制并像单选框一样渲染的项目。

属性类型默认值
asChild
布尔值
value*
字符串
无默认值
禁用
布尔值
无默认值
onSelect
函数
无默认值
文本值
字符串
无默认值
数据属性
[data-state]"已选中" |"未选中"
[data-highlighted]

高亮时显示

[data-disabled]

禁用时显示

项目指示器

当父级 Menubar.CheckboxItemMenubar.RadioItem 被选中时渲染。您可以直接设置此元素的样式,也可以将其用作包装器来放入图标,或者两者都做。

属性类型默认值
asChild
布尔值
forceMount
布尔值
无默认值
数据属性
[data-state]"已选中" |"未选中"

分隔符

用于在菜单栏菜单中视觉上分隔项目。

属性类型默认值
asChild
布尔值

子菜单

包含子菜单的所有部件。

属性类型默认值
defaultOpen
布尔值
无默认值
open
布尔值
无默认值
onOpenChange
函数
无默认值

子菜单触发器

打开子菜单的项目。必须在 Menubar.Sub 内部渲染。

属性类型默认值
asChild
布尔值
禁用
布尔值
无默认值
文本值
字符串
无默认值
数据属性
[data-state]"打开" |"关闭"
[data-highlighted]

高亮时显示

[data-disabled]

禁用时显示

子菜单内容

子菜单打开时弹出的组件。必须在 Menubar.Sub 内部渲染。

属性类型默认值
asChild
布尔值
loop
布尔值
onEscapeKeyDown
函数
无默认值
onPointerDownOutside
函数
无默认值
onFocusOutside
函数
无默认值
onInteractOutside
函数
无默认值
forceMount
布尔值
无默认值
sideOffset
数字
0
alignOffset
数字
0
avoidCollisions
布尔值
collisionBoundary
Boundary
[]
collisionPadding
number | Padding
0
arrowPadding
数字
0
sticky
枚举
"部分"
hideWhenDetached
布尔值
数据属性
[data-state]"打开" |"关闭"
[data-side]"左" |"右" |"底部" |"顶部"
[data-align]"开始" |"结束" |"居中"
[data-orientation]"垂直" |"水平"
CSS 变量描述
--radix-menubar-content-transform-origin从内容和箭头位置/偏移量计算的 transform-origin
--radix-menubar-content-available-width触发器和边界边缘之间的剩余宽度
--radix-menubar-content-available-height触发器和边界边缘之间的剩余高度
--radix-menubar-trigger-width触发器的宽度
--radix-menubar-trigger-height触发器的高度

示例

使用子菜单

您可以通过结合 Menubar.Sub 及其部件来创建子菜单。

<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item></Menubar.Item>
<Menubar.Item></Menubar.Item>
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>Sub menu →</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>Sub menu item</Menubar.Item>
<Menubar.Item>Sub menu item</Menubar.Item>
<Menubar.Arrow />
</Menubar.SubContent>
</Menubar.Portal>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.Item></Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>

使用禁用项目

您可以通过 data-disabled 属性为禁用项目添加特殊样式。

// index.jsx
import { Menubar } from "radix-ui";
import "./styles.css";
export default () => (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item className="MenubarItem" disabled>
</Menubar.Item>
<Menubar.Item className="MenubarItem"></Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
/* styles.css */
.MenubarItem[data-disabled] {
color: gainsboro;
}

使用分隔符

使用 Separator 部件在项目之间添加分隔符。

<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item></Menubar.Item>
<Menubar.Separator />
<Menubar.Item></Menubar.Item>
<Menubar.Separator />
<Menubar.Item></Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>

使用标签

使用 Label 部件来帮助标记一个部分。

<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Label>Label</Menubar.Label>
<Menubar.Item></Menubar.Item>
<Menubar.Item></Menubar.Item>
<Menubar.Item></Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>

使用复选框项目

使用 CheckboxItem 部件来添加可以勾选的项目。

import * as React from "react";
import { CheckIcon } from "@radix-ui/react-icons";
import { Menubar } from "radix-ui";
export default () => {
const [checked, setChecked] = React.useState(true);
return (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item></Menubar.Item>
<Menubar.Item></Menubar.Item>
<Menubar.Separator />
<Menubar.CheckboxItem checked={checked} onCheckedChange={setChecked} >
<Menubar.ItemIndicator>
<CheckIcon />
</Menubar.ItemIndicator>
Checkbox item
</Menubar.CheckboxItem>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
};

使用单选框项目

使用 RadioGroupRadioItem 部件来添加可以在其他项目中选中的项目。

import * as React from "react";
import { CheckIcon } from "@radix-ui/react-icons";
import { Menubar } from "radix-ui";
export default () => {
const [color, setColor] = React.useState("blue");
return (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.RadioGroup value={color} onValueChange={setColor}>
<Menubar.RadioItem value="red">
<Menubar.ItemIndicator>
<CheckIcon />
</Menubar.ItemIndicator>
Red
</Menubar.RadioItem>
<Menubar.RadioItem value="blue">
<Menubar.ItemIndicator>
<CheckIcon />
</Menubar.ItemIndicator>
Blue
</Menubar.RadioItem>
</Menubar.RadioGroup>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
};

使用复杂项目

您可以在 Item 部件中添加额外的装饰元素,例如图像。

import { Menubar } from "radix-ui";
export default () => (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content>
<Menubar.Item>
<img src="" />
Adolfo Hess
</Menubar.Item>
<Menubar.Item>
<img src="" />
Miyah Myles
</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);

约束内容/子内容大小

您可能希望约束内容(或子内容)的宽度,使其与触发器(或子触发器)宽度匹配。您可能还希望约束其高度,使其不超过视口。

我们公开了几个 CSS 自定义属性,例如 --radix-menubar-trigger-width--radix-menubar-content-available-height 以支持这一点。使用它们来约束内容尺寸。

// index.jsx
import { Menubar } from "radix-ui";
import "./styles.css";
export default () => (
<Menubar.Root>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className="MenubarContent" sideOffset={5}>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Root>
);
/* styles.css */
.MenubarContent {
width: var(--radix-menubar-trigger-width);
max-height: var(--radix-menubar-content-available-height);
}

原点感知动画

我们公开了一个 CSS 自定义属性 --radix-menubar-content-transform-origin。使用它从其基于 sidesideOffsetalignalignOffset 和任何碰撞计算出的原点来动画内容。

// index.jsx
import { Menubar } from "radix-ui";
import "./styles.css";
export default () => (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className="MenubarContent"></Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
/* styles.css */
.MenubarContent {
transform-origin: var(--radix-menubar-content-transform-origin);
animation: scaleIn 0.5s ease-out;
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}

碰撞感知动画

我们公开了 data-sidedata-align 属性。它们的值将在运行时更改以反映碰撞。使用它们来创建碰撞和方向感知动画。

// index.jsx
import { Menubar } from "radix-ui";
import "./styles.css";
export default () => (
<Menubar.Root>
<Menubar.Menu>
<Menubar.Trigger></Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className="MenubarContent"></Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
);
/* styles.css */
.MenubarContent {
animation-duration: 0.6s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.MenubarContent[data-side="top"] {
animation-name: slideUp;
}
.MenubarContent[data-side="bottom"] {
animation-name: slideDown;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

可访问性

遵循菜单按钮 WAI-ARIA 设计模式和使用roving tabindex来管理菜单项之间的焦点移动。

键盘交互

描述
空格
当焦点在 Menubar.Trigger 上时,打开菜单栏并聚焦第一个项目。
当焦点在一个项目上时,激活聚焦的项目。
回车
当焦点在 Menubar.Trigger 上时,打开关联的菜单。
当焦点在一个项目上时,激活聚焦的项目。
向下箭头
当焦点在 Menubar.Trigger 上时,打开关联的菜单。
当焦点在一个项目上时,将焦点移动到下一个项目。
向上箭头
当焦点在一个项目上时,将焦点移动到上一个项目。
向右箭头向左箭头
当焦点在 Menubar.Trigger 上时,将焦点移动到下一个或上一个项目。
当焦点在 Menubar.SubTrigger 上时,根据阅读方向打开或关闭子菜单。
当焦点在 Menubar.Content 中时,打开菜单栏中的下一个菜单。
Esc
关闭当前打开的菜单并将焦点移动到其 Menubar.Trigger.
上一个标签
下一个导航菜单