Components

菜单栏

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

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;

Features

    可控或非控。

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

    支持项、标签、项组。

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

    自定义侧面、对齐、偏移量、碰撞处理。

    可选择性地渲染指向箭头。

    焦点完全管理。

    完整的键盘导航。

    支持提前输入。

安装

从命令行安装组件。

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 参考

Root

包含菜单栏的所有部分。

PropTypeDefault
asChild
boolean
false
defaultValue
string
No default value
value
string
No default value
onValueChange
function
No default value
dir
enum
No default value
loop
boolean
false

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

PropTypeDefault
asChild
boolean
false
value
string
No default value

Trigger

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

PropTypeDefault
asChild
boolean
false
Data attributeValues
[data-state]"open" | "closed"
[data-highlighted]

当突出显示时存在

[data-disabled]

当禁用时存在

Portal

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

PropTypeDefault
forceMount
boolean
No default value
container
HTMLElement
document.body

Content

菜单打开时弹出的组件。

PropTypeDefault
asChild
boolean
false
loop
boolean
false
onCloseAutoFocus
function
No default value
onEscapeKeyDown
function
No default value
onPointerDownOutside
function
No default value
onFocusOutside
function
No default value
onInteractOutside
function
No default value
forceMount
boolean
No default value
side
enum
"bottom"
sideOffset
number
0
align
enum
"center"
alignOffset
number
0
avoidCollisions
boolean
true
collisionBoundary
Boundary
[]
collisionPadding
number | Padding
0
arrowPadding
number
0
sticky
enum
"partial"
hideWhenDetached
boolean
false
Data attributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS VariableDescription
--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触发器的高度

Arrow

一个可选的箭头元素,可以与菜单栏菜单一起呈现。这可以帮助在视觉上将触发器与 Menubar.Content 关联。必须在 Menubar.Content 内部呈现。

PropTypeDefault
asChild
boolean
false
width
number
10
height
number
5

Item

包含菜单栏项的组件。

PropTypeDefault
asChild
boolean
false
disabled
boolean
No default value
onSelect
function
No default value
textValue
string
No default value
Data attributeValues
[data-highlighted]

当突出显示时存在

[data-disabled]

当禁用时存在

Group

用于分组多个 Menubar.Item

PropTypeDefault
asChild
boolean
false

Label

用于呈现标签。无法使用箭头键聚焦。

PropTypeDefault
asChild
boolean
false

CheckboxItem

可以控制并呈现为复选框的项。

PropTypeDefault
asChild
boolean
false
checked
boolean | 'indeterminate'
No default value
onCheckedChange
function
No default value
disabled
boolean
No default value
onSelect
function
No default value
textValue
string
No default value
Data attributeValues
[data-state]"checked" | "unchecked"
[data-highlighted]

当突出显示时存在

[data-disabled]

当禁用时存在

RadioGroup

用于分组多个 Menubar.RadioItem

PropTypeDefault
asChild
boolean
false
value
string
No default value
onValueChange
function
No default value

RadioItem

可以控制并呈现为单选的项。

PropTypeDefault
asChild
boolean
false
value*
string
No default value
disabled
boolean
No default value
onSelect
function
No default value
textValue
string
No default value
Data attributeValues
[data-state]"checked" | "unchecked"
[data-highlighted]

当突出显示时存在

[data-disabled]

当禁用时存在

ItemIndicator

当父 Menubar.CheckboxItemMenubar.RadioItem 被选中时呈现。您可以直接样式化此元素,或者将其用作放置图标的包装器,或两者兼而有之。

PropTypeDefault
asChild
boolean
false
forceMount
boolean
No default value
Data attributeValues
[data-state]"checked" | "unchecked"

Separator

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

PropTypeDefault
asChild
boolean
false

Sub

包含子菜单的所有部分。

PropTypeDefault
defaultOpen
boolean
No default value
open
boolean
No default value
onOpenChange
function
No default value

SubTrigger

打开子菜单的项。必须在 Menubar.Sub 内部呈现。

PropTypeDefault
asChild
boolean
false
disabled
boolean
No default value
textValue
string
No default value
Data attributeValues
[data-state]"open" | "closed"
[data-highlighted]

当突出显示时存在

[data-disabled]

当禁用时存在

SubContent

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

PropTypeDefault
asChild
boolean
false
loop
boolean
false
onEscapeKeyDown
function
No default value
onPointerDownOutside
function
No default value
onFocusOutside
function
No default value
onInteractOutside
function
No default value
forceMount
boolean
No default value
sideOffset
number
0
alignOffset
number
0
avoidCollisions
boolean
true
collisionBoundary
Boundary
[]
collisionPadding
number | Padding
0
arrowPadding
number
0
sticky
enum
"partial"
hideWhenDetached
boolean
false
Data attributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
[data-orientation]"vertical" | "horizontal"
CSS VariableDescription
--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>子菜单 →</Menubar.SubTrigger>
<Menubar.Portal>
<Menubar.SubContent>
<Menubar.Item>子菜单项</Menubar.Item>
<Menubar.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>标签</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>
复选框项
</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>
红色
</Menubar.RadioItem>
<Menubar.RadioItem value="blue">
<Menubar.ItemIndicator>
<CheckIcon />
</Menubar.ItemIndicator>
蓝色
</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="" />
阿道夫·黑斯
</Menubar.Item>
<Menubar.Item>
<img src="" />
宫谷·麦尔斯
</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 设计模式 并使用 游走的 tabindex 来管理菜单项之间的焦点移动。

键盘交互

KeyDescription
Space
当焦点位于 Menubar.Trigger 时,打开菜单栏并聚焦第一个项。
当焦点位于某个项时,激活该项。
Enter
当焦点位于 Menubar.Trigger 时,打开相关的菜单。
当焦点位于某个项时,激活该项。
ArrowDown
当焦点位于 Menubar.Trigger 时,打开相关的菜单。
当焦点位于某个项时,移动焦点到下一个项。
ArrowUp
当焦点位于某个项时,移动焦点到上一个项。
ArrowRightArrowLeft
当焦点位于 Menubar.Trigger 时,移动焦点到下一个或上一个项。
当焦点位于 Menubar.SubTrigger 时,打开或关闭子菜单,具体取决于阅读方向。
当焦点位于 Menubar.Content 时,打开菜单栏中的下一个菜单。
Esc
关闭当前打开的菜单,并将焦点移动到其 Menubar.Trigger