菜单栏
把应用的命令放在一行常驻的、有名称的菜单(文件、编辑、视图……)里,让它们随时可用;每个菜单在自己的标题下方打开,方向键沿这一行移动,并进入各个菜单。
- 类别
- 选择
- 令牌
comp.menubar- 层级
- 组合
- 构成自
- Menu
- Web
tanrei-menubarschedule - SwiftUI
TanreiMenubarradio_button_unchecked - Jetpack Compose
TanreiMenubarradio_button_unchecked - Flutter
TanreiMenubarradio_button_unchecked - React Native
Menubarradio_button_unchecked
生成自 docs/status/status.json, specs/menubar.spec.json
编辑器的菜单
用它之前,请先读这一节。菜单栏用于应用的命令,除此之外几乎别无用途。
- 它不是网站的导航。 一排带下拉列表的栏目是导航:它们是链接,位于导航地标之内,用 Tab 到达。菜单栏则告诉辅助技术:在它里面的所有内容之间移动,用的是方向键而不是 Tab,而且每个条目都是命令。用户在一组页面链接上遇到这些,就会被误导。请使用应用栏、导航栏或导航抽屉。
- 它不是工具栏。 工具栏容纳的是一按就执行的按钮。菜单栏是一行菜单。它可以有一两个直接起作用的条目(见下文),而整行都是这种条目时,那就是工具栏。沿菜单栏移动绝不会执行任何操作,所以用户可以放心地四处查看。
- 它不是一排各自打开菜单的按钮。 那样的一排看起来一样,用起来却不行:方向键不会带着打开的菜单移到相邻的按钮,指针移过去时打开的菜单也不会跟着换,辅助技术读到的是一个个独立的按钮,而不是一个分两级的菜单。
- 它不是标签页。 没有哪个标题会处于选中状态。菜单关闭后,不会留下任何选中的东西。
- 它不是单个菜单。 为数不多的几个选项,应当放进由按钮打开的一个菜单。
编辑器、电子表格、绘图或规划工具这样的应用型产品,命令多得无法全部作为按钮显示在屏幕上。菜单栏就是容纳所有这些命令的地方:它们归在几个名称之下,这些名称用户学一次就能记住,每个命令旁边还带着自己的快捷键。
它是始终可见的一行标题。每个标题在自己下方打开一个 Tanrei 的菜单,里面是菜单自己的各种项:操作、链接项、复选项和单选项、组、子菜单和快捷键。在标题旁边,这一行还可以放栏项:一个立即执行的操作,或者一个链接。整行在焦点顺序中是一个停靠点。方向键沿这一行移动并进入菜单,在菜单里则可以直接前往相邻的条目。
菜单栏自己没有填充、边线和层级(Elevation)。它放在哪里由你决定,通常是窗口顶部已有的界面外框里。
在 Web 上,它是包着若干 <tanrei-menubar-menu> 元素的 <tanrei-menubar>,直接起作用的条目用 <tanrei-menubar-item>。其他平台尚未实现它。
给菜单栏一个 label,给每个菜单一个一两个词的 label。菜单的项放在它里面,写法与放进菜单时完全相同。监听这些项自己的事件:菜单栏不会给它们添加任何事件。
每个菜单都有自己的 open。菜单栏设置它并触发 toggle,所以你不用记录哪个菜单是打开的。你也可以自己设置它,用你自己的按键打开某个菜单。
<tanrei-menubar label="应用菜单" overflow-label="更多菜单">
<tanrei-menubar-menu label="文件">
<tanrei-menu-item label="新建" shortcut="mod+n"></tanrei-menu-item>
<tanrei-menu-item label="打开…" shortcut="mod+o"></tanrei-menu-item>
<tanrei-submenu label="最近打开">
<tanrei-menu-item label="季度报告"></tanrei-menu-item>
</tanrei-submenu>
<tanrei-menu-item label="保存" shortcut="mod+s"></tanrei-menu-item>
</tanrei-menubar-menu>
<tanrei-menubar-menu label="视图">
<tanrei-menu-checkbox-item label="标尺" checked></tanrei-menu-checkbox-item>
</tanrei-menubar-menu>
<tanrei-menubar-menu label="格式" disabled>…</tanrei-menubar-menu>
</tanrei-menubar>
<script type="module">
import '@tanrei/web/components/menubar';
const bar = document.querySelector('tanrei-menubar');
// 一个监听器就能接收每一项的 click。命令执行之后,把焦点移到它所作用的对象上。
bar.addEventListener('click', (event) => {
if (event.target.localName !== 'tanrei-menu-item') return;
run(event.target.label);
editor.focus();
});
// 项上显示的快捷键由你来绑定。打开菜单的按键也一样。
addEventListener('keydown', (event) => {
if (event.altKey && event.key === 'f') bar.querySelector('[label="文件"]').open = true;
});
</script>
<!-- 在元素定义之前预留菜单栏的高度,这样页面不会跳动。 -->
<style>
tanrei-menubar:not(:defined) {
display: block;
min-block-size: calc(var(--tanrei-comp-menubar-container-height) + 2 * var(--tanrei-comp-menubar-bar-padding-block));
}
</style>import { Menubar, MenubarMenu, MenuItem, MenuCheckboxItem, Submenu } from '@tanrei/react';
<Menubar label="应用菜单" overflowLabel="更多菜单">
<MenubarMenu label="文件" onToggle={(event) => track('File', event.detail.open)}>
<MenuItem label="新建" shortcut={['mod+n']} onClick={newDocument} />
<MenuItem label="打开…" shortcut={['mod+o']} onClick={openDocument} />
<Submenu label="最近打开">
{recent.map((file) => <MenuItem key={file.id} label={file.name} onClick={() => open(file)} />)}
</Submenu>
</MenubarMenu>
<MenubarMenu label="视图">
<MenuCheckboxItem label="标尺" checked={ruler} onChange={(event) => setRuler(event.detail.checked)} />
</MenubarMenu>
<MenubarMenu label="格式" disabled={!selection}>…</MenubarMenu>
</Menubar>在 Web 上,脚本运行之前,标题和操作不会被画出来:它们的文字是属性,每个命令也都是你的脚本。链接项则不同。它是你自己写的锚元素,所以在脚本运行之前,以及在脚本根本不运行的地方,它都是一个能用的链接。
使用栏项之前,先看什么时候不该用它。栏项看起来与打开菜单的标题一模一样,所以每多一个栏项,用户就要多犹豫一次:按下去会发生什么。
- 不用于大多数命令。 命令应当放在菜单里,在那里它有快捷键、图标,还有帮助理解它的相邻项。栏项只留给用户希望一按就完成的一两件事(“运行”“分享”),或者他们要去的一两个地方(“文档”)。
- 不用于任何无法撤销的操作。 栏项在执行之前不做任何询问,也没有破坏性操作的外观。删除或放弃内容的命令应当放在菜单里。
- 不用于可以打开和关闭的选项。 标题上没有放对勾的位置。把它放进同一个菜单栏的某个菜单里,作为复选项或单选项。
- 不要让整个菜单栏都是栏项。 只有操作的一行是工具栏,只有链接的一行是导航。这两种情况都会照常绘制、照常工作,并会在调试构建中报告。
- 不要用看不出用途的词。 在同时容纳两者的菜单栏中,每个菜单的标题末尾有一个向下的小 V 形箭头,栏项没有,但仍要靠标签说明按下后会发生什么。操作用动词命名,链接用它通向的地方命名。
操作写作 <tanrei-menubar-item label="运行">。按下、Enter 或 Space 会触发一次它的 click。如果菜单栏有菜单正打开着,这个菜单会先关闭,所以你的处理函数运行时没有菜单显示。焦点留在栏项上,除非你的处理函数把它移走。
链接项是同一个元素,里面包着你自己的锚元素:<tanrei-menubar-item><a href="/docs/">文档</a></tanrei-menubar-item>。链接之所以是链接的一切,都属于锚元素:锚元素的文本就是标签,href、target、rel 和 aria-current="page" 都写在锚元素上。这个元素在它周围画出一个标题,并让它成为菜单栏按键移动时的一个停靠点。
click就是锚元素自己的 click。用preventDefault()取消它,链接就不会被跟随,客户端路由需要的仅此而已。路由自己的链接组件可以直接作为子元素使用。- 带修饰键的点击、中键点击和链接的上下文菜单都交给浏览器处理,与任何链接一样。Space 与 Enter 一样会跟随链接项,因为这一行的其他每个条目都响应 Space。
target="_blank"会在标签之后画出图标集的open_in_new,并加上noopener。请用元素上的target-label把这一点用文字说出来,或者用setWords({ targetLabel: … })为整个产品只给一次。- 锚元素上的
aria-current="page"标出用户当前所在的页面。它以菜单为用户所在的行绘制的那种填充来绘制,标签保持自身的字重,这样当前页面在菜单栏和菜单中看起来一样。它绝不会看起来像已打开菜单的标题。它由你来设置,菜单栏从不设置。 - 禁用的链接项,是在元素上写
disabled,并且锚元素不带href。
<link rel="stylesheet" href="@tanrei/web/menubar.css" />
<tanrei-menubar label="应用菜单" overflow-label="更多">
<tanrei-menubar-menu label="文件">…</tanrei-menubar-menu>
<tanrei-menubar-menu label="编辑">…</tanrei-menubar-menu>
<tanrei-menubar-item label="运行"></tanrei-menubar-item>
<tanrei-menubar-item><a href="/docs/">文档</a></tanrei-menubar-item>
<tanrei-menubar-item target-label="在新标签页中打开">
<a href="https://example.com/source" target="_blank">源代码</a>
</tanrei-menubar-item>
<tanrei-menubar-item><a href="/history/" aria-current="page">历史记录</a></tanrei-menubar-item>
</tanrei-menubar>
<script type="module">
import '@tanrei/web/components/menubar';
// 操作:它自己的 click,无论它在行里,还是折叠进了溢出项。
document.querySelector('tanrei-menubar-item[label="运行"]').addEventListener('click', run);
// 链接项:锚元素的 click。取消它,导航就由你接管。
document.querySelector('tanrei-menubar-item > a[href="/docs/"]').addEventListener('click', (event) => {
event.preventDefault();
router.push('/docs/');
});
</script>import { Menubar, MenubarMenu, MenubarItem } from '@tanrei/react';
import { Link } from 'your-router';
<Menubar label="应用菜单" overflowLabel="更多">
<MenubarMenu label="文件">…</MenubarMenu>
<MenubarItem label="运行" onClick={run} />
{/* 子元素是路由自己的链接:它渲染出锚元素,并接管导航。 */}
<MenubarItem><Link href="/docs/">文档</Link></MenubarItem>
<MenubarItem targetLabel="在新标签页中打开">
<a href="https://example.com/source" target="_blank">源代码</a>
</MenubarItem>
</Menubar>@tanrei/web/menubar.css 用于脚本运行之前的那段时间,也用于浏览器不运行任何脚本的读者。它把链接项在菜单栏的位置上排成一行,用标题的文本样式绘制,每一个都像普通链接那样可以跟随,也可以用 Tab 到达。这时菜单和操作还不存在。菜单栏运行起来之后,会原地接管这些锚元素。
一行放不下所有标题时,把其余的折叠进一个溢出项
构成
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | "" | Names the bar for assistive technology ("Application menu"), in the product's language. Never drawn. Required. It is this bar's own name and is not read from the product's words. |
overflowLabel | string | "" | The accessible name of the overflow item ("More menus", or "More" where bar items may fold into it too), in the product's language. Never drawn. Needed whenever the bar may be narrower than its entries, which on the web is always. |
条目
该组件重复排列的条目种类。测试样例按顺序列出条目;各平台以各自的惯用方式为每个种类命名。
menu
One menu of the bar: its title in the row and the items that open below it. Its items are the menu component's own item kinds (item, checkbox-item, radio-item, submenu and group, as the menu spec defines them), which the bar presents in a Tanrei Menu; a fixture cannot fill them.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | "" | The title's text, and the name of the title and of the menu it opens ("File"). Required. One or two words. |
open | boolean | false | Whether this menu is shown. It is the menu's own, as a submenu item's is: the bar sets it when the menu is opened or closed and emits toggle; the product may set it to open or close the menu itself. At most one menu of a bar is open. |
disabled | boolean | false | Prevents the menu from opening. The title stays in the row and reachable with the arrow keys. |
| 事件 | 说明 |
|---|---|
toggle | The menu was opened or closed by the person — from its title, by the arrow keys from a neighbouring menu, by a pointer moving along the bar, by Escape, Tab, a press outside or an item's activation, or as a submenu of the overflow item. Carries the new open value. Not emitted for a change the product made to open. |
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 条目 | tanrei-menubar-menu | TanreiMenubarMenu | TanreiMenubarMenu | TanreiMenubarMenu | MenubarMenu |
label · 属性 | label | title | text | label | label |
open · 属性 | open | isPresented | expanded | controller | open |
disabled · 属性 | disabled | disabled | enabled | enabled | disabled |
toggle · 事件 | toggle | isPresented | onExpandedChange | onOpenChanged | onToggle |
| 备注 | <tanrei-menubar-menu label="File"> holds the menu's item elements as its children (tanrei-menu-item, tanrei-menu-checkbox-item, tanrei-menu-radio-item, tanrei-submenu, tanrei-menu-group), as tanrei-menu holds its own; it forwards them into a tanrei-menu in its shadow root, anchored to itself, and moves none of them. open reflects; toggle is a CustomEvent whose detail.open is the new value, fired on the element, and bubbles. The items' own click, change and toggle bubble through it, so one listener on the bar takes them all (a submenu's toggle and a bar menu's are told apart by their target). | TanreiMenubarMenu(_ title:, isPresented:) { TanreiMenuItem(…) … }: the content builder takes the menu's item views; the binding is optional, and writing it is toggle. disabled follows .disabled(_:). | TanreiMenubarMenu(text, expanded, onExpandedChange, enabled) { TanreiMenuItem(…) … } in the bar's content: the caller keeps expanded (state hoisting), and an overload without the pair keeps it itself. enabled is the inverse flag. | TanreiMenubarMenu(label:, menuChildren: […], controller:, onOpenChanged:, enabled: true), as a SubmenuButton in a MenuBar: the optional MenuController opens and closes it; enabled is the inverse flag. | An entry of the bar's menus array: { label, items: […], open?, disabled?, onToggle? }, where items is the menu's own array of entries, told apart by their kind field as in <Menu>. |
item
A bar item: an entry of the bar that acts by itself instead of opening a menu. It is an action, run at once; with an href it is a link, which goes somewhere. It is the menu's own item kind brought up to the row, with a title's look and none of a row's columns.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | "" | The bar item's text and its accessible name ("Run", "Documentation"). Required. One or two words that say what it does at once — a verb for an action, the place for a link — because nothing in its look tells it from a title that opens a menu. |
disabled | boolean | false | Prevents activation. The bar item stays in the row and reachable with the arrow keys, as a disabled title does; a disabled link item cannot be followed (MBR-39). |
href | string | "" | The destination: an address as the platform's links take one (a URL; on the web a path or a fragment as well). Not empty, it makes the bar item a link item (a control with a destination is a link): the platform's own link, exposed as a menu item, drawn exactly as an action is and reached with the same keys; a plain activation emits click and opens the destination (MBR-35, MBR-36). Empty, the bar item is an action and the product acts on click. As the menu item's href (MNU-53). |
target | same | new | "same" | Where the destination opens, as the Button's target. same: where the person is. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which the bar item marks with the glyph open_in_new after its label and says through targetLabel (MBR-37). Ignored without href. |
targetLabel | string | "" | What assistive technology reads after the link item's name while target is new ("opens in a new tab"), as the Button's targetLabel: Tanrei ships no words of its own. Required while target is new; ignored otherwise. |
current | boolean | false | The link item names the place the person is already at (the current page), as the Link's current. It is exposed as the current item, drawn with the fill of the menu's row the person is on (comp.menubar.selected.container.color, which aliases comp.menu.current.container.color) at its label's own weight, and stays a link (MBR-38). The product sets it; the bar never does, and at most one bar item of a bar is current. Ignored without href. |
| 事件 | 说明 |
|---|---|
click | The bar item was activated by pointer, touch, Enter, Space or assistive technology — in the row or, while it is folded, in the overflow item's menu. A link item emits it before its destination opens; a product that takes the navigation itself (a client-side router) cancels it (MBR-36). |
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 条目 | tanrei-menubar-item | TanreiMenubarItem | TanreiMenubarItem | TanreiMenubarItem | MenubarItem |
label · 属性 | label | title | text | label | label |
disabled · 属性 | disabled | disabled | enabled | enabled | disabled |
href · 属性 | href | destination | href | href | href |
target · 属性 | target | target | target | target | target |
targetLabel · 属性 | targetLabel | targetLabel | targetLabel | targetSemanticLabel | targetLabel |
current · 属性 | current | isCurrent | current | isCurrent | current |
click · 事件 | click | action | onClick | onPressed | onPress |
| 备注 | An action is <tanrei-menubar-item label="Run"></tanrei-menubar-item>: the host is the entry — role="menuitem" with neither aria-haspopup nor aria-expanded, aria-disabled, and the bar's roving tabindex — and draws its label in its shadow root as tanrei-menubar-menu does; click is the host's own click, which Enter and Space produce through click() and a disabled host swallows. A link item is the same element around the product's own anchor, which stays in the document (as the breadcrumbs' levels do): <tanrei-menubar-item><a href="/docs/">Documentation</a></tanrei-menubar-item>. The anchor is the entry: the element writes role="menuitem" and the roving tabindex on it and role="none" on the host, slots it into the title's container so that the whole container is the link, and draws the state layer, the focus ring and the mark around it in its shadow root. A link item's props are the anchor's own, as the Link's are: its text is label; href; target in HTML's spelling (_blank is new, to which the element adds noopener in rel; anything else is same); aria-current="page" is current; rel and download are the anchor's and web-only. The host's properties of the same names read the anchor and, when the product sets one, write it there, so that the address is held in one place; a label attribute on a host that holds an anchor is ignored. targetLabel is the host's target-label attribute, else the product's words; the element writes it as the anchor's aria-description unless the product wrote one there. disabled is the host's attribute in both forms, and for a link item it is the one statement that the entry is disabled: <tanrei-menubar-item disabled><a>Documentation</a></tanrei-menubar-item>. The product writes that anchor without href, as the Link's disabled anchor is written: only an anchor with no address is one the browser cannot follow by a modified click, its context menu or a drag, and the element never takes away or puts back an address the product wrote. On it the element writes aria-disabled="true" beside role="menuitem" and keeps it in the roving order, so that the arrow keys still reach it (MBR-39); before the bar runs it is plain text in the row, which is what a link that cannot be followed is. The two mismatches are defined and reported in debug builds. A disabled host whose anchor still has an href is drawn and exposed disabled, emits no click and has its plain activation prevented, but what the browser itself gives an address is still there, and the report says to take the href away. An anchor without href under a host that is not disabled is an entry without a destination — an action (MBR-44) whose entry is still the anchor and whose click is the anchor's — and the report says to write an action with label. click is the anchor's own click and bubbles through the host: cancelling it (preventDefault()) cancels the navigation, a router's own link component rendered as the child needs nothing more, a click with a modifier key or the middle button is the browser's, Enter with a modifier key is passed to the anchor, and Space calls the anchor's click(). An href attribute on a host that holds no anchor is ignored and reported: the destination is the anchor's, so that the link exists before the element is defined (MBR-45). An href on tanrei-menubar-menu is ignored and reported (MBR-44). React: MenubarItem renders the element around its children and adds nothing; a router's link is the child. | TanreiMenubarItem(_ title:, action:) is an action. destination: URL? = nil makes it a SwiftUI Link (the link trait in place of the button's), opened by the environment's openURL after action, which an app that routes addresses itself answers with its own OpenURLAction; target: TanreiLinkTarget = .same and targetLabel (the accessibility hint), as TanreiMenuItem's. isCurrent: Bool = false adds the selected trait and the emphasized weight. disabled follows .disabled(_:). | TanreiMenubarItem(text, onClick, enabled = true, href: String? = null, target: TanreiLinkTarget = TanreiLinkTarget.Same, targetLabel: String = "", current: Boolean = false). With href it is a link: opened through LocalUriHandler.current after onClick (an app that routes addresses itself provides its own UriHandler); Compose has no link role for a clickable element, so the node drops Role.Button and carries none; targetLabel is appended to the state description, as TanreiMenuItem's. current sets selected in the semantics and the emphasized weight. enabled is the inverse flag. | TanreiMenubarItem(label:, onPressed:, enabled: true, href: Uri? = null, target: TanreiLinkTarget.same, targetSemanticLabel:, isCurrent: false). With href it is a link (Semantics(link: true) with the address as its linkUrl), opened by the nearest TanreiLinkOpener after onPressed; targetSemanticLabel is the semantics hint, as TanreiMenuItem's. isCurrent sets selected in the semantics and the emphasized weight. enabled is the inverse flag. | An entry of the bar's menus array whose kind is 'item': { kind: 'item', label, onPress?, href?, target?, targetLabel?, current?, disabled? }. With href it is a link: onPress receives an event whose preventDefault() keeps the bar from calling Linking.openURL(href); target is "same" | "new" and targetLabel the accessibilityHint, as <Menu>'s item. current sets aria-current="page" where the platform has it and the selected state elsewhere. |
状态
行为
以 EARS 形式写成的需求。每个平台的实现都按这些 ID 逐条测试。需求原文取自规格文件,原样以英文呈现。
| ID | 需求 |
|---|---|
MBR-1 | THE menubar SHALL draw one row across the full inline size its parent gives it: comp.menubar.bar.color behind the whole row, and on it the titles of its menus, one for each menu in the order given, from the start edge, comp.menubar.bar.gap apart, inset by comp.menubar.bar.padding-inline from the row's start and end edges and by comp.menubar.bar.padding-block above and below. It SHALL NOT position itself, stick to an edge, raise itself or draw an edge of its own: the product places it, normally at the top of its window or in the chrome that is already there, and the bar stays in view because that chrome does. |
MBR-2 | THE menubar SHALL draw each title as a container at least comp.menubar.container.height tall and as wide as its label with comp.menubar.container.padding-inline on each side, with corners comp.menubar.container.shape, each at most half the container's height, and its label centered in it on one line in comp.menubar.label.typescale; it SHALL NOT wrap, shrink or truncate a title's label: a title that the row has no room for is folded whole (MBR-21). |
MBR-3 | THE menubar SHALL draw a title from the condition it is in: expanded while its menu is open, otherwise unselected; a bar item from the unselected condition, and a link item that is current from the selected one (MBR-38). WHILE a title is at rest its container SHALL take the container color of its condition (comp.menubar.unselected.container.color, comp.menubar.selected.container.color, comp.menubar.expanded.container.color) drawn over the bar, and its label the label color of its condition (comp.menubar.unselected.label.color, comp.menubar.selected.label.color, comp.menubar.expanded.label.color). No title is selected: the expanded condition says which menu is shown now and ends when that menu closes, and the selected condition is the current page's alone (MBR-38). |
MBR-4 | WHILE a title is hovered, keyboard-focused or pressed THE menubar SHALL render its container, label, the overflow item's glyph and opacity from the component tokens of its condition in the highest-precedence active state, with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. |
MBR-5 | WHEN a title receives keyboard focus THE menubar SHALL show the focus ring around the title's container without animation; focus gained by pointer or touch SHALL NOT show it. The bar SHALL leave room for the ring: it SHALL NOT clip it at its own edges. |
MBR-6 | WHEN the menubar title is pressed by pointer, touch, Enter or Space THE menubar title SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner. |
MBR-7 | WHILE pressed THE menubar title SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; label, icon, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.menubar.motion.feedback-duration with motion.state-easing, fades over motion.state-duration with motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced. |
MBR-8 | THE menubar title SHALL ease its container corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README. |
MBR-9 | WHEN the user activates an enabled title whose menu is closed — by a press with a pointer, a tap, Enter, Space, Down Arrow or Up Arrow while the title has focus — THE menubar SHALL open that title's menu without delay, close any other open menu of the bar (MBR-10), set the menu's open to true and emit the menu's toggle once with true. The open menu SHALL be a Tanrei Menu anchored to the title, below it with the start edges aligned, comp.menu.surface.offset from it, flipping above and shifting along the title as MNU-2 says, drawn from the menu's own tokens and holding the menu's own item kinds (actions, link items, checkbox and radio items, groups, submenus, shortcuts), every one of which SHALL behave as the menu's spec says except where MBR-13 to MBR-15 say otherwise. Focus SHALL move into the menu as MNU-1 says — to its first enabled item, or to its last enabled item when the key was Up Arrow, and to its first or last item where none is enabled (MNU-43) — showing the focus ring only when the input was a key. WHILE its menu is open THE menubar SHALL draw the title in the expanded condition (MBR-3) in whatever interaction state it is in. |
MBR-10 | THE menubar SHALL show at most one of its menus at a time. WHEN a menu opens while another menu of the bar is open THE menubar SHALL close the other first, every submenu of it with it, set its open to false and emit its toggle once with false. WHEN the user presses the title of the open menu with a pointer or taps it THE menubar SHALL close that menu, set its open to false, emit its toggle once with false and keep focus on the title. WHEN the product sets a menu's open THE menubar SHALL open or close that menu as an activation would, without emitting that menu's toggle for the product's own change; IF the product sets open on more than one menu THE menubar SHALL show the first of them in the order given, and in debug builds report it. |
MBR-11 | WHILE a menu of the bar is open, WHEN a pointer that can hover moves onto another enabled title THE menubar SHALL open that title's menu at once, with no delay, and close the one that was open (MBR-10), moving focus into the new menu as MNU-1 says without showing a focus ring; a pointer on a disabled title, or on the bar between titles, SHALL leave the open menu open. WHILE no menu of the bar is open a pointer on a title SHALL only render it hovered and SHALL open nothing. Touch has no hover: a tap on another title opens its menu (MBR-9). |
MBR-12 | THE menubar SHALL be one Tab stop: the title that had keyboard focus last or whose menu was opened last, by any input, whichever came later, or else its first title. WHILE a title has keyboard focus and no menu is open, the arrow key that points to the inline end (Right Arrow in a left-to-right layout, Left Arrow in a right-to-left one) and the one that points to the inline start SHALL move focus to the next and the previous title, wrapping at the ends, Home and End SHALL move it to the first and the last title, and a printable character typed with no modifier but Shift SHALL move it to the next title after the focused one, wrapping, whose label begins with that character, compared without regard to letter case, and nowhere when no label does; none of these keys SHALL open a menu. A disabled title and the overflow item (MBR-21) SHALL be reached by these keys like any other title, so that a person hears every menu the bar has. Tab and Shift+Tab SHALL move focus out of the bar, to what follows and what precedes it. Beyond these keys and those of MBR-9 THE menubar SHALL handle no key while no menu is open, and SHALL NOT let a key it handles scroll or act on what is around the bar. |
MBR-13 | WHILE a menu of the bar is open and focus is inside it, at any level, WHEN the user presses the arrow key that points to the inline end on an item that is not a submenu item THE menubar SHALL close that menu and every submenu of it, open the menu of the next enabled title, wrapping at the ends and passing over disabled titles, and move focus to that menu's first enabled item with the focus ring shown; WHEN the user presses the arrow key that points to the inline start while focus is in the menu's own surface, not in a submenu, THE menubar SHALL do the same towards the previous enabled title. These two keys replace what MNU-51 says for a menu that stands alone; on a submenu item the inline-end key still opens its submenu (MNU-43), and in a submenu the inline-start key still closes that submenu alone (MNU-48). WHERE the bar has one enabled title only, these keys SHALL do nothing and SHALL NOT let the key scroll or act on what is behind the menu. Each menu closed and opened this way SHALL have its open set and its toggle emitted once (MBR-9, MBR-10). |
MBR-14 | WHEN the user presses Escape or the platform's back while a menu of the bar is open and no submenu of it is open THE menubar SHALL close that menu, set its open to false, emit its toggle once with false and return focus to its title, showing the focus ring when the input was a key, so that the arrow keys go on along the bar. WHEN the user presses Tab or Shift+Tab while a menu of the bar is open, at any level, THE menubar SHALL close it the same way and SHALL let focus move on out of the bar, to what follows the bar for Tab and to what precedes it for Shift+Tab, never to another title. WHEN the user presses outside every surface of the open menu and outside the bar, or focus leaves the bar and the open menu, THE menubar SHALL close the menu the same way and SHALL NOT take focus back. WHILE no menu is open THE menubar SHALL NOT handle Escape. |
MBR-15 | WHEN the user activates an item of an open menu that closes its menu (an action or a link item, at any level: MNU-6, MNU-54) THE menubar SHALL close the menu, every submenu with it, set its open to false, emit its toggle once with false after the item's own event, and return focus to the menu's title, unless the product moved focus elsewhere while handling the item's event, in which case THE menubar SHALL leave focus where the product put it. A checkbox or radio item activated with Space leaves its menu open, as MNU-6 says. |
MBR-16 | THE menubar SHALL add no event to an item of its menus and take none away: an item's click, a checkbox item's change, a group's change and a submenu item's toggle are emitted as the menu's spec says, by the item or the group. THE menubar SHALL NOT listen for a keyboard shortcut that an item shows, nor run an item when its shortcut is pressed: the menu draws the shortcut and exposes it (MNU-31), and binding the keys is the product's. THE menubar SHALL define no access key of its own — no underlined letter in a title, no key that moves focus to the bar from elsewhere — and SHALL leave a product free to add one by moving focus to a title or setting a menu's open. |
MBR-17 | WHERE a title's visual size is below the minimum target THE menubar SHALL extend its hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size; a platform that cannot tell the input type SHALL use sys.target.touch. Between two neighbouring titles the hit areas SHALL meet at the middle of comp.menubar.bar.gap and SHALL never overlap; a title narrower than the target SHALL be given the room its target needs by moving its neighbours apart, not by overlapping them. |
MBR-18 | WHILE a menu's disabled is true THE menubar SHALL render its title's container, label and opacity from the disabled component tokens of the unselected condition, SHALL NOT open its menu by any input or by the product's open, SHALL close it if it is open (MBR-10), SHALL NOT emit its toggle for an activation, SHALL keep the title reachable by the keys of MBR-12, and SHALL expose the title as a menu item that opens a menu, with the disabled state. |
MBR-19 | THE menubar SHALL expose itself with the menubar role, named by label, with a horizontal orientation; each title as a menu item that opens a menu, named by its menu's label, with whether that menu is open, the disabled state where it has it, and its position among the bar's titles and their number; and each open menu as a menu named by its title, its items exposed as the menu's spec says (MNU-24). The overflow item SHALL be one such title, named by overflowLabel (MBR-22). WHERE a platform has no menubar role THE menubar SHALL expose a group named by label that holds, in order, one button for each title, each saying that it opens a menu and whether that menu is open, and SHALL say so in its README. |
MBR-20 | WHILE label is empty THE menubar SHALL still render and work, and in debug builds report that it has no accessible name; WHILE a menu's label is empty THE menubar SHALL still draw its title, as an empty container of the title's height and paddings, keep it reachable and operable, and in debug builds report that the title has no name. IF the menubar holds neither a menu nor a bar item THE menubar SHALL draw the bar with nothing on it, take no focus, and in debug builds report it. IF a menu holds no item THE menubar SHALL still draw its title and open its surface, empty, on an activation, SHALL keep focus on the title while that menu is open, SHALL answer the inline arrow keys on that title as MBR-13 says for a menu's own surface and Escape and Tab as MBR-14 says, and in debug builds report it; WHILE such a menu is folded into the overflow item (MBR-22) it SHALL be a submenu item that draws its row, takes focus and opens nothing, as MNU-52 says of a submenu with no item. IF it holds something that is neither a menu nor a bar item (MBR-32) THE menubar SHALL give it no title, no key and no place in the row, and in debug builds report it: a field, a control of the product's own or an entry that holds a state would take the keys the bar moves focus with and would not be a menu item. |
MBR-21 | IF the titles need more inline size than the row has THE menubar SHALL keep every title at its own size, SHALL NOT wrap the row onto a second line, scroll it, shrink it or cut a label, and SHALL fold menus out of the row from its end, as few as make the rest fit, into one overflow item drawn as the last title of the row: the glyph more_horiz of the system's icon set at comp.menubar.icon.size, in the icon color of its condition and state (comp.menubar.unselected.icon.color at rest, comp.menubar.expanded.icon.color while its menu is open), drawn through the icon helper with sys.icon.family.unselected, sys.icon.fill.unselected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, centered in a container at least comp.menubar.container.height wide and tall with the corners of MBR-2. IF the first title does not fit beside the overflow item THE menubar SHALL fold every menu, so that the row is the overflow item alone. WHILE every title fits THE menubar SHALL draw no overflow item. THE menubar SHALL decide again what fits, at once and without a motion, whenever its inline size, its menus, a label, the text size or the layout direction changes. |
MBR-22 | THE menubar SHALL treat the overflow item as a title in every respect — its conditions, states, target, keys and hover (MBR-3 to MBR-17) — and SHALL open from it a Tanrei Menu that lists the folded menus in the order given, each as a submenu item named by that menu's label and holding that menu's items unchanged, a disabled menu as a disabled submenu item; these SHALL behave as MNU-42 to MNU-52 say, and the level the fold adds SHALL NOT count towards the depth MNU-52 reports. WHEN a folded menu's submenu opens or closes THE menubar SHALL set that menu's open and emit its toggle once, as it does for a menu opened from its own title, so that open and toggle mean the same wherever the menu is shown. WHEN a change of MBR-21 folds or unfolds the menu that is open THE menubar SHALL close it first (MBR-10) and, if focus was inside it, move focus to the title it is now reached from: its own, or the overflow item. WHILE overflowLabel is empty and the product's words do not give it THE menubar SHALL still draw the overflow item and let it work, SHALL expose it without a name, and in debug builds report the missing word once. |
MBR-23 | THE menubar SHALL supply no word of its own: its name is label and each title is its menu's label, given on the instance; the overflow item's name is overflowLabel, read from the instance and, where that is empty, from the product's words under the key menubarOverflowLabel; the words of every item are the menu's own props. A word nobody gave SHALL NOT be replaced by one in any language. |
MBR-24 | THE menubar SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the menubar is shown THE menubar SHALL draw itself for the new direction at once, without a motion of its own, keeping its focus, the title its Tab stop stands on and which menu is open, the open menu placed again by the new direction (MNU-2) and what fits decided again (MBR-21). |
MBR-25 | WHERE the layout direction is right-to-left THE menubar SHALL lay out its titles from the right edge in the order given, fold menus from the left end of the row and draw the overflow item at the left end, open each menu below its title with their right edges aligned, and move focus along the bar with Left Arrow to the next title and Right Arrow to the previous one (MBR-12, MBR-13); the order of focus and of reading SHALL stay the order given. The overflow item's glyph SHALL be mirrored exactly when the system's icon set marks its name as mirrored, by the icon helper and not by the bar. |
MBR-26 | THE menubar SHALL lay out each title's label as text of its own direction, isolated from the text around it: it takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word keeps its own order in a right-to-left layout and reorders nothing beside it; THE menubar SHALL still place each title by the layout direction, not by the text's own. |
MBR-27 | WHILE the text size is enlarged THE menubar SHALL grow each title with its label — the container's height is a minimum, and its width follows the label — and the row with its titles, keeping comp.menubar.bar.gap, comp.menubar.bar.padding-inline, comp.menubar.bar.padding-block and comp.menubar.container.padding-inline as they are, and SHALL decide again what fits (MBR-21), so that at twice the text size in a window 320 wide every menu is still reached, from its title or through the overflow item, with nothing cut and no scrolling sideways. |
MBR-28 | WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE menubar SHALL draw each title's label and the overflow item's glyph in the platform's button text color, a disabled title in its disabled text color, the focus ring in its highlight color, and SHALL mark the title whose menu is open, and a hovered or pressed title, by the platform's highlight colors or by an outline of sys.border.hairline or more around the title's container, never by a fill the platform may remove; it SHALL draw no fill for the bar. |
MBR-29 | WHEN the colors of a title change between states or conditions THE menubar SHALL animate them over comp.menubar.motion.state-duration with comp.menubar.motion.state-easing, and SHALL change them at once where motion is reduced. WHEN one menu of the bar takes the place of another (MBR-11, MBR-13) THE menubar SHALL remove the one and show the other at once, with neither the close nor the open motion of MNU-4, so that moving along the bar does not flicker; the menu that opens from a bar with no menu open, and the last one to close, SHALL move as MNU-4 says. |
MBR-30 | WHEN a menu is added to the bar, removed from it, reordered or given another label or disabled value while the bar is shown THE menubar SHALL lay the row out again at once, without a motion; a menu that is removed while open SHALL be closed without emitting its toggle; WHEN the title that holds focus or the Tab stop is removed THE menubar SHALL move it to the title that followed, or to the one before where none follows, and SHALL NOT let focus fall out of the bar while a title remains. |
MBR-31 | WHILE a menu of the bar is open THE menubar SHALL NOT trap focus, SHALL NOT make the rest of the screen inert and SHALL draw no scrim: its menus are operated overlays that are not modal (MNU-25), and the bar itself is part of the page, not an overlay. A press outside that closes a menu SHALL also reach what is under it where the menu's own platform form lets it (MNU-5). |
MBR-32 | THE menubar SHALL take two kinds of entry in its row, in the order given and mixed freely: a menu, whose title opens it (MBR-9), and a bar item, which acts by itself — an action that is run at once or, WHILE its href is not empty, a link that goes somewhere (MBR-35). THE menubar SHALL give a bar item its place in the row among the titles and SHALL treat it as a title in every respect but one, that it has no menu: it is drawn as MBR-2 draws a title, from its own label; it is in the unselected condition, or in the selected one while it is a link item that is current (MBR-38), and never in the expanded one (MBR-3); it takes a title's states, focus ring, press answer, corners and target (MBR-4 to MBR-8, MBR-17), its hit area meeting its neighbours' as theirs meet; it is counted with the titles wherever this spec orders, measures, folds or mirrors them or lays them out again (MBR-1, MBR-21, MBR-24 to MBR-27, MBR-30); and it is one of the stops of the bar's one Tab stop, reached by the inline arrow keys, Home, End and a typed character exactly as a title is, a disabled one included, and it is where the Tab stop stands when it had keyboard focus or was activated last (MBR-12). WHILE the bar holds both menus and bar items THE menubar SHALL tell a menu's title from a bar item at rest by the indicator MBR-47 draws after each menu's title, and assistive technology by what MBR-42 says; WHILE every entry is a menu it SHALL draw none, the titles needing nothing to be told from each other. |
MBR-33 | WHEN the user activates an enabled bar item that is not a link item — by a press with a pointer, a tap, Enter or Space while it has focus, or the default action of assistive technology — THE menubar SHALL first close the menu of the bar that is open, if one is, as MBR-10 says, and SHALL then emit the bar item's click exactly once; it SHALL open nothing, and SHALL keep focus on the bar item, moved there without a focus ring when the input was a pointer or a touch, unless the product moved focus elsewhere while handling click, in which case it SHALL leave focus where the product put it. THE menubar SHALL activate a bar item for nothing else: not for focus arriving on it, not for a pointer over it, and not for the arrow keys, Home, End or a typed character that reach it, so that moving along the bar runs no command. |
MBR-34 | WHILE a bar item has keyboard focus THE menubar SHALL do nothing for Down Arrow and Up Arrow, which open a menu from a title and have none to open here, and SHALL NOT let them scroll or act on what is around the bar, so that the same two keys never open a menu on one entry of the row and move the page on the next; it SHALL NOT handle Escape (MBR-14), and Tab and Shift+Tab SHALL leave the bar as MBR-12 says. |
MBR-35 | WHILE a bar item's href is not empty THE menubar SHALL make it a link item, as MNU-53 makes an item of a menu one: the platform's own link to href (the web's anchor element; SwiftUI's Link; on Compose, Flutter and React Native an element with the platform's link semantics whose address is opened by the platform's opener), as BTN-22 says for a link button, so that whatever the platform gives a link is the platform's, and not the bar's to draw or to take over. WHERE the platform has a menubar role THE menubar SHALL expose a link item with the menu item role, on the link itself, and not with the link role, for the reason MNU-53 gives for a menu: the bar's role, an entry's position in it and its keys hold only while everything in the row is a menu item. THE menubar SHALL draw a link item exactly as it draws an action, from the same tokens in every state, with no look of its own for a link and none for a destination already visited, SHALL give the bar item's whole container and its hit area (MBR-17) to the link, and SHALL keep it one stop of the bar's Tab stop like any entry, never a Tab stop of its own. |
MBR-36 | WHEN the user activates an enabled link item as an entry of the bar is activated — by a click with the primary button and no modifier key, a touch, Enter or Space with no modifier key, or the default action of assistive technology — THE menubar SHALL close the menu of the bar that is open, if one is (MBR-10), emit the bar item's click exactly once, and then open href where target names, unless the product cancelled that click, in which case it SHALL open nothing: the product's own router has taken the navigation, as MNU-54 says of a menu's link item. It SHALL show the pressed state as MBR-6 and MBR-7 say and SHALL NOT hold the navigation back for sys.motion.duration.press-hold. Space SHALL activate a link item exactly as Enter does and SHALL scroll nothing, as MNU-54 says and for its reasons: a link item is exposed and operated as a menu item, nothing in its look tells it from the action beside it, and Space activates every other entry of the row. For every other activation the platform gives a link — on the web a click or Enter with a modifier key, a middle click and the link's context menu, on Apple platforms a long press that previews — THE menubar SHALL leave the link to the platform, SHALL prevent, replace or imitate none of it, and SHALL close no menu for it. |
MBR-37 | WHILE a bar item is a link item and its target is new THE menubar SHALL open href in a new context, as BTN-24 says for a link button (on the web a new tab or window that is given no handle to the page that opened it), and SHALL draw the glyph open_in_new of the system's icon set after the label, comp.menubar.container.gap from it and inside the container's paddings, at comp.menubar.icon.size, in the icon color of the bar item's state (comp.menubar.unselected.icon.color at rest), through the icon helper as MBR-21 draws the overflow item's glyph — the helper mirrors it in a right-to-left layout because the icon set marks its name — and hidden from assistive technology; the bar item SHALL then be as wide as its label, the gap and the glyph need. THE menubar SHALL expose targetLabel as the bar item's description — read from the bar item and, where that is empty, from the product's words under the key targetLabel — and WHILE neither gives it SHALL still render and in debug builds report that a link that opens a new context does not say so. WHILE target is same THE menubar SHALL draw no mark and SHALL ignore targetLabel. |
MBR-38 | WHILE a link item's current is true THE menubar SHALL expose it as the current item of its set (on the web the current page; on a platform without that state, the selected state), SHALL keep it a link that takes focus and can be followed, and SHALL draw it from the selected condition (MBR-3): its container in comp.menubar.selected.container.color, which is the fill of the menu's row the person is on (comp.menu.current.container.color), in every contrast scheme, and its label and mark in that condition's colors, in every state, at its label's own weight, so that the place the person is on is drawn in the bar as a menu draws it (MNU-58). It is never mistaken for the title of an open menu: the expanded condition has colors of its own, and the current page is a fact about where the person is, not a menu that is shown. THE menubar SHALL NOT set current itself on any activation: where the product is, is the product's to say (NVB-31). IF more than one bar item is current THE menubar SHALL draw and expose each as given and in debug builds report it; WHILE a bar item's href is empty THE menubar SHALL ignore its current and in debug builds report it. |
MBR-39 | WHILE a bar item's disabled is true THE menubar SHALL render its container, label, mark and opacity from the disabled component tokens of the unselected condition, SHALL NOT emit its click or act on any input, SHALL keep it reachable by the keys of MBR-12 as MBR-18 keeps a disabled title, and SHALL expose it with the disabled state; a disabled link item SHALL NOT be a link that can be followed, as MNU-56 says of a menu's: it SHALL keep the exposure MBR-35 gives it, with the disabled state and no destination, and SHALL open nothing by any means the platform gives a link (on the web the anchor carries no href, so there is no context menu entry, no drag and no new tab for it). |
MBR-40 | WHEN an inline arrow key of MBR-13 is pressed in an open menu and the next enabled entry in that direction — wrapping at the ends, and passing over disabled bar items as it passes over disabled titles — is a bar item THE menubar SHALL close the open menu and every submenu of it, set its open to false and emit its toggle once with false, and SHALL move focus to that bar item with the focus ring shown; it SHALL open nothing and SHALL NOT activate the bar item. No menu is then open, so the keys that follow are those of MBR-12 and MBR-34 and open nothing: THE menubar SHALL keep no memory that a menu was open, and moving on from the bar item SHALL NOT open the next title's menu — a menu follows focus along the bar only while a menu is shown (MBR-11, MBR-13). WHERE the open menu's title is the bar's only enabled entry these keys SHALL do nothing, as MBR-13 says. |
MBR-41 | WHILE a menu of the bar is open, WHEN a pointer that can hover moves onto a bar item THE menubar SHALL render the bar item hovered and SHALL leave the open menu open and focus where it is, as MBR-11 says of a disabled title: a bar item has no menu to show in the other's place, and a menu that closed under a pointer on its way along the row would have to be opened again with a press at the next title. A press on the bar item then activates it (MBR-33, MBR-36), which closes the open menu first. WHILE no menu of the bar is open a pointer on a bar item SHALL only render it hovered. |
MBR-42 | THE menubar SHALL expose each bar item as a menu item named by its label, which SHALL NOT say that it opens a menu and SHALL carry no expanded state (on the web neither aria-haspopup nor aria-expanded, in any value), with the disabled state where it has it and with its position among the bar's entries and their number, the titles, the bar items and the overflow item counted together in the order drawn; a link item as MBR-35 says, with its description where it opens a new context (MBR-37) and with whether it is current (MBR-38). WHERE a platform has no menubar role and exposes the group of MBR-19 instead THE menubar SHALL put in that group, in the order given among the titles' buttons, a button for an action and, for a link item, the platform's link with its link role or trait in place of the button's. |
MBR-43 | WHEN MBR-21 folds entries out of the row THE menubar SHALL fold bar items as it folds menus — whole, from the end, in the order given, whatever their kind — and SHALL list a folded bar item in the overflow item's menu (MBR-22) at its place in that order among the folded menus' submenu items, not as a submenu but as an item of that menu: an action as the menu's own item (MNU-6) and a link item as the menu's own link item, with the same href, target and targetLabel (MNU-53 to MNU-56), each named by the bar item's label and disabled while the bar item is. WHEN the user activates a folded bar item THE menubar SHALL emit the bar item's own click exactly once — the same event of the same bar item, which a product cancels for a link item as MBR-36 says — so that click means the same wherever the bar item is shown, and SHALL then close the overflow item's menu and return focus to the overflow item as MBR-15 says, unless the product moved focus elsewhere while handling click, a link item opening its destination after that; the activations MNU-54 leaves to the platform SHALL leave the overflow item's menu open. The click comes before the closing here and after it in the row (MBR-33, MBR-36), and each is the order its place already has: in the row the menu that is open is another entry's, and an entry of the row that is pressed has the other's menu closed before it answers, as a pressed title does (MBR-10); in the fold the bar item is an item of the menu that is open, and an item's own event comes before its menu closes (MNU-6, MBR-15). In both places THE menubar SHALL emit click before it settles where focus rests, so that focus the product moved while handling click is left where the product put it. A folded link item whose target is new SHALL be marked and described as the menu marks and describes its own (MNU-55), and a folded link item that is current SHALL be the menu's own current link item (MNU-58): exposed as current, and filled as the menu fills the row the person is on (MBR-38). THE menubar SHALL keep the overflow item a title that opens a menu, whatever is folded into it, so that it never acts by itself: WHERE everything folded is bar items its menu holds items and no submenu, and WHERE the row is the overflow item alone the bar is that one menu. |
MBR-44 | WHEN a bar item's target names a value the menubar does not define THE menubar SHALL treat it as same, and in debug builds report the unknown value. IF a bar item's href is only white space, or names a script to run rather than a place to go (the web's javascript: scheme), or cannot be read as an address by the platform, THE menubar SHALL make the bar item an action without a destination, and in the last two cases report it in debug builds, as BTN-26 says for a button. WHILE a bar item's href is empty THE menubar SHALL ignore its target and targetLabel. WHILE a bar item's label is empty THE menubar SHALL still draw it as an empty container of a title's height and paddings, keep it reachable and operable, and in debug builds report that it has no name, as MBR-20 says of a title. THE menubar SHALL give a destination to a bar item alone: WHERE a platform lets one be written on a menu THE menubar SHALL ignore it, keep the menu what it is, and in debug builds report it. THE menubar SHALL take no entry that holds a state: a checkbox or a radio entry directly in the row is refused as MBR-20 says, because a choice that is switched needs a mark a title has no place for, and it belongs in a menu of the bar, where the menu's checkbox and radio items mark it. IF the menubar holds bar items and no menu THE menubar SHALL still render and work, and in debug builds report it: a row of actions alone is a toolbar, and a row of links alone is navigation. |
MBR-45 | WHERE the platform shows the product's own markup before the component runs (the web, before its elements are defined and where scripting is off) THE menubar SHALL leave every link item a link that works: its anchor is the product's own element in the document, shown in the bar's place in the order given and followed by the browser as any link is, with no menubar role, no menu item role and no roving focus on anything until the bar runs. WHEN the bar begins to run THE menubar SHALL take those anchors as its link items where they stand, SHALL add to them the role and the focus order MBR-35 and MBR-12 give, and SHALL NOT copy, move or replace them; a link item that is folded (MBR-43) SHALL keep its anchor in the document, not displayed. A menu and an action SHALL show nothing there: their words are the bar's to draw and their work is the product's script. |
MBR-46 | WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE menubar SHALL draw an action and its states as MBR-28 draws a title, and a link item's label and mark in the platform's link color, a disabled one in its disabled text color: the colors are then the person's and not the design system's, and what they chose for a link holds for a link in a bar as it does anywhere. A current link item, whose fill the mode removes, SHALL be marked by a line of sys.border.thick inside its container's edge in the platform's link color. |
MBR-47 | WHILE the menubar holds at least one bar item and at least one menu THE menubar SHALL end the title of each menu (not a bar item's, and not the overflow item, which is a glyph of its own, MBR-21) with the glyph expand_more of the system's icon set, comp.menubar.container.gap after its label and inside the container's paddings, at comp.menubar.indicator.size, in the icon color of the title's condition and state (comp.menubar.unselected.icon.color at rest, comp.menubar.expanded.icon.color while its menu is open), drawn through the icon helper with sys.icon.family.unselected, sys.icon.fill.unselected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, never mirrored and not turned while the menu is open, and hidden from assistive technology, so that a person sees before pressing which entries open a menu and which act at once; the title SHALL then be as wide as its label, the gap and the glyph need, and MBR-21 SHALL measure it so. WHILE every entry of the bar is a menu THE menubar SHALL draw no indicator. Whether the bar mixes the two kinds SHALL be decided from all its entries, folded or not, so that folding never adds or takes away an indicator; WHEN entries are added or removed so that this changes THE menubar SHALL draw or remove the indicators at once, without a motion, and decide again what fits (MBR-21, MBR-30). A menu folded into the overflow item is its submenu item and draws that item's indicator (MNU-46), not this one. IF the icon set cannot draw the glyph yet THE menubar SHALL draw nothing in its place, never its name, and keep its room. |
MBR-48 | WHERE the platform has ARIA attributes (the web), the menubar's host and an action bar item's host carries its role (MBR-19, MBR-42) itself, so what the product writes on it is not moved, but for a name: WHILE label is not empty THE menubar SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that label is its whole name although the platform gives aria-labelledby the precedence, and WHEN label is emptied SHALL give them back as they were written; WHILE label is empty a name the product gives so — aria-label, or aria-labelledby naming an element — SHALL be its name and SHALL NOT be reported as having none in debug builds (MBR-20, MBR-44). WHILE a bar item is folded into the overflow item, or holds the product's own anchor (MBR-35), its host is nothing, and THE menubar SHALL hold them as well, giving them back when the item is an action in the row again. WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaLabelledByElements) to null, THE menubar SHALL take it away. |
各平台名称对照
对等并不意味着 API 完全相同。同一份契约,由各平台以自己的惯用方式表达。
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 组件 | tanrei-menubar | TanreiMenubar | TanreiMenubar | TanreiMenubar | Menubar |
label · 属性 | label | label | contentDescription | semanticLabel | accessibilityLabel |
overflowLabel · 属性 | overflowLabel | overflowLabel | overflowContentDescription | overflowSemanticLabel | overflowAccessibilityLabel |
| 备注 | The shadow-root form: the bar is a control whose parts the product does not write. <tanrei-menubar label="Application menu" overflow-label="More menus"> holds tanrei-menubar-menu children in its default slot. The host has role="menubar" and aria-label; each tanrei-menubar-menu host is its title — role="menuitem", aria-haspopup="menu", aria-expanded, aria-disabled, and the roving tabindex (0 on the Tab stop, -1 on the others), as the toolbar sets it on its items — and draws its label in its shadow root. The menu it opens is a tanrei-menu in that shadow root (a manual popover in the top layer, named by aria-label from the title's label, since aria-controls cannot cross a shadow root), opened with placement bottom-start; the bar tells it through an internal channel, not part of the public surface, to hand the inline arrow keys of MBR-13 back, to skip its open and close motion when it replaces a neighbour (MBR-29), and not to report the fold's level (MBR-22). The overflow item and its tanrei-menu are in the bar's shadow root. A folded tanrei-menubar-menu is assigned to a slot of that menu and draws itself there as a tanrei-submenu in its own shadow root, with its children forwarded into it one slot each: a slot of the bar's shadow root can hold only the bar's own children, so a submenu drawn there could show no divider and no gap between a folded menu's groups (MNU-23, MNU-36). The product's elements stay where the product wrote them, and open and toggle mean what they mean in the row. A folded menu with no item is a submenu item that opens nothing (MBR-20, MNU-52). What fits is measured with a ResizeObserver on the row, on a hidden copy of every title and of the overflow item drawn from the tokens, and on the gap, so that deciding it asks for no layout of its own (MBR-21). Hover switching listens for pointerenter with pointerType mouse or pen. In forced-colors mode the open title is drawn with Highlight and HighlightText (MBR-28). Without script, and before the elements are defined, a menu and an action draw nothing — their words are attributes and their work is the product's script — and a link item's anchor is a plain link that works (MBR-45): a rule of the package's stylesheet for tanrei-menubar:not(:defined) lays those anchors out as one row in a title's text style, color and paddings, with no underline; a product reserves the bar's block size (tanrei-menubar:not(:defined) { display: block; min-block-size: … }, from the tokens' custom properties) to keep the page from moving. The inline attribute, for tests, draws an open menu in place below its title, as the menu's does. The host is display: block. Bar items: tanrei-menubar-item children take their places in the default slot among the menus, in document order. The roving tabindex stands on whichever element is the entry — a menu's host, an action's host, or a link item's anchor — and the bar's keys, type-ahead and Tab stop read them as one list. The fold measures and folds a bar item as it does a menu, and shows a folded one as it shows a folded menu: the tanrei-menubar-item is assigned to a slot of the overflow tanrei-menu, at its place in document order among the folded menus, and draws itself there as a tanrei-menu-item in its own shadow root (with href, target and target-label for a link item, and through the internal channel the current state and weight of MBR-43). The product's anchor of a folded link item stays where it is in the document, a child of the host that nothing displays. A plain activation of that row becomes one click on the product's own element — the host for an action; for a link item the anchor, whose click() is called after the row's own navigation is prevented — and the row's own click does not reach the page as a second one: a listener on the product's element, or above it, hears the activation once, and cancelling it there cancels the navigation. It is heard while the overflow menu is still shown, which closes after it (MBR-43); in the row a menu that is open is closed, with its toggle, before the click is delivered (MBR-33, MBR-36). A click with a modifier key or the middle button on that row is the browser's, on the row's own anchor, which has the same address. Hover while a menu is open ignores pointerenter on a bar item (MBR-41). In forced-colors mode a link item's anchor keeps LinkText (MBR-46). While the bar holds both kinds of entry, each tanrei-menubar-menu title ends in the indicator part, the icon set's expand_more (MBR-47); a current link item is drawn from the selected condition (MBR-38). | TanreiMenubar(_ label: String = "", overflowLabel: String = "") { TanreiMenubarMenu("File") { … } … }, drawn in the window: a custom Layout that measures the titles and folds from the end (MBR-21), each title presenting its items with .tanreiMenu. Focus along the bar uses @FocusState with .onKeyPress (and .onMoveCommand where key presses are not delivered); hover switching uses .onContinuousHover. SwiftUI has no menubar trait: the bar is .accessibilityElement(children: .contain) named by the label, and each title a button with the expanded state (MBR-19's fallback). An empty overflowLabel reads the product's words from the environment. It does not fill the system's own menu bar: on macOS and on iPadOS with a hardware keyboard the product also declares its commands with .commands { CommandMenu … }, and on an iPhone it normally shows no menubar at all. TanreiMenubarItem stands in the same builder among the menus and is laid out, focused and folded as a title is; a folded one is a TanreiMenuItem of the overflow menu. SwiftUI has no menubar trait, so in MBR-19's group an action is a button and a link item carries the link trait (MBR-42). Where the system has a bar of its own the product declares an action there too, as it does its menus' commands. | TanreiMenubar(contentDescription = "Application menu", overflowContentDescription = "More menus") { TanreiMenubarMenu("File") { … } … }: a Layout that measures the titles and folds from the end, each title showing its items in a TanreiMenu. One focus group (Modifier.focusGroup()), moved with onPreviewKeyEvent and FocusRequesters; hover switching uses PointerEventType.Enter for a mouse or stylus. Compose semantics have no menubar role: the bar is a traversal group named by contentDescription, each title Role.Button with expand / collapse actions and its state (MBR-19's fallback). An empty overflowContentDescription reads the product's words from the composition local. TanreiMenubarItem stands in the same content among the menus and is measured, focused and folded as a title is; a folded one is a TanreiMenuItem of the overflow menu. In MBR-19's group an action is Role.Button and a link item drops the role, as every Compose link form does (MBR-42). | TanreiMenubar(semanticLabel:, overflowSemanticLabel:, entries: [TanreiMenubarMenu(…), TanreiMenubarItem(…)]), built with the widgets library on TanreiMenuAnchor, not on Material's MenuBar: a RenderBox that measures the titles and folds from the end. Focus is a FocusTraversalGroup with Shortcuts and Actions for the keys; hover switching uses a MouseRegion and ignores touch. Semantics: SemanticsRole.menuBar and menuItem where the SDK has them, otherwise a container named by the label with buttons that expose their expanded state. An empty overflowSemanticLabel reads the product's words from the inherited widget. A desktop product also gives its commands to PlatformMenuBar where the system has a bar of its own. A TanreiMenubarItem in entries is measured, focused and folded as a title is; a folded one is a TanreiMenuItem of the overflow menu. Semantics: SemanticsRole.menuItem where the SDK has it, with link: true and the address for a link item; otherwise a button, or a link, in the named container (MBR-42). | <Menubar accessibilityLabel="Application menu" overflowAccessibilityLabel="More menus" menus={[{ label: 'File', items: […] }, …]} />: role="menubar" with role="menuitem" titles that carry aria-expanded; titles are measured with onLayout and folded from the end; each menu is shown on the overlay layer that TanreiProvider renders, as <Menu> is. Keys reach it where the platform delivers them (a hardware keyboard on a tablet, react-native-web, desktop targets); hover switching uses onHoverIn where the platform has a pointer. An empty overflowAccessibilityLabel reads the product's words from the context. The app-wide direction is followed, as the README says for every component. The menus array also takes bar items — entries whose kind is 'item'; an entry without a kind is a menu. A bar item is role="menuitem" without aria-expanded, measured and folded as a title is; a folded one is an entry of the overflow <Menu>. |
令牌
| 令牌 | 引用 | 解析值(浅色) |
|---|---|---|
--tanrei-comp-menubar-bar-color | — | |
--tanrei-comp-menubar-bar-gap | sys.space.space25 | 2px |
--tanrei-comp-menubar-bar-padding-block | sys.space.space50 | 4px |
--tanrei-comp-menubar-bar-padding-inline | sys.space.space50 | 4px |
--tanrei-comp-menubar-container-gap | sys.space.space50 | 4px |
--tanrei-comp-menubar-container-height | — | 40px |
--tanrei-comp-menubar-container-padding-inline | sys.space.space150 | 12px |
--tanrei-comp-menubar-container-shape | sys.shape.corner.small | 6px |
--tanrei-comp-menubar-disabled-container-shape | — | 6px |
--tanrei-comp-menubar-disabled-opacity | — | 1 |
--tanrei-comp-menubar-expanded-container-color | sys.color.surface-container-high | |
--tanrei-comp-menubar-expanded-disabled-container-color | — | |
--tanrei-comp-menubar-expanded-disabled-icon-color | — | |
--tanrei-comp-menubar-expanded-disabled-label-color | — | |
--tanrei-comp-menubar-expanded-disabled-state-layer-color | — | |
--tanrei-comp-menubar-expanded-focused-container-color | — | |
--tanrei-comp-menubar-expanded-focused-icon-color | — | |
--tanrei-comp-menubar-expanded-focused-label-color | — | |
--tanrei-comp-menubar-expanded-focused-state-layer-color | — | |
--tanrei-comp-menubar-expanded-hovered-container-color | — | |
--tanrei-comp-menubar-expanded-hovered-icon-color | — | |
--tanrei-comp-menubar-expanded-hovered-label-color | — | |
--tanrei-comp-menubar-expanded-hovered-state-layer-color | — | |
--tanrei-comp-menubar-expanded-icon-color | sys.color.on-surface | |
--tanrei-comp-menubar-expanded-label-color | sys.color.on-surface | |
--tanrei-comp-menubar-expanded-pressed-container-color | — | |
--tanrei-comp-menubar-expanded-pressed-icon-color | — | |
--tanrei-comp-menubar-expanded-pressed-label-color | — | |
--tanrei-comp-menubar-expanded-pressed-state-layer-color | — | |
--tanrei-comp-menubar-expanded-state-layer-color | comp.menubar.expanded.label.color | |
--tanrei-comp-menubar-focused-container-shape | — | 6px |
--tanrei-comp-menubar-focused-opacity | — | 1 |
--tanrei-comp-menubar-hovered-container-shape | — | 6px |
--tanrei-comp-menubar-hovered-opacity | — | 1 |
--tanrei-comp-menubar-icon-size | sys.icon.medium | 24px |
--tanrei-comp-menubar-indicator-size | sys.icon.small | 20px |
--tanrei-comp-menubar-motion-feedback-duration | sys.motion.role.feedback | 450ms |
--tanrei-comp-menubar-motion-state-duration | sys.motion.role.state.default | 250ms |
--tanrei-comp-menubar-motion-state-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-menubar-pressed-container-shape | — | 6px |
--tanrei-comp-menubar-pressed-opacity | — | 1 |
--tanrei-comp-menubar-selected-container-color | comp.menu.current.container.color | |
--tanrei-comp-menubar-selected-disabled-container-color | — | |
--tanrei-comp-menubar-selected-disabled-icon-color | — | |
--tanrei-comp-menubar-selected-disabled-label-color | — | |
--tanrei-comp-menubar-selected-disabled-state-layer-color | — | |
--tanrei-comp-menubar-selected-focused-container-color | — | |
--tanrei-comp-menubar-selected-focused-icon-color | — | |
--tanrei-comp-menubar-selected-focused-label-color | — | |
--tanrei-comp-menubar-selected-focused-state-layer-color | — | |
--tanrei-comp-menubar-selected-hovered-container-color | — | |
--tanrei-comp-menubar-selected-hovered-icon-color | — | |
--tanrei-comp-menubar-selected-hovered-label-color | — | |
--tanrei-comp-menubar-selected-hovered-state-layer-color | — | |
--tanrei-comp-menubar-selected-icon-color | comp.menu.current.leading-icon.color | |
--tanrei-comp-menubar-selected-label-color | comp.menu.current.label.color | |
--tanrei-comp-menubar-selected-pressed-container-color | — | |
--tanrei-comp-menubar-selected-pressed-icon-color | — | |
--tanrei-comp-menubar-selected-pressed-label-color | — | |
--tanrei-comp-menubar-selected-pressed-state-layer-color | — | |
--tanrei-comp-menubar-selected-state-layer-color | comp.menubar.selected.label.color | |
--tanrei-comp-menubar-unselected-container-color | — | |
--tanrei-comp-menubar-unselected-disabled-container-color | — | |
--tanrei-comp-menubar-unselected-disabled-icon-color | — | |
--tanrei-comp-menubar-unselected-disabled-label-color | — | |
--tanrei-comp-menubar-unselected-disabled-state-layer-color | — | |
--tanrei-comp-menubar-unselected-focused-container-color | — | |
--tanrei-comp-menubar-unselected-focused-icon-color | — | |
--tanrei-comp-menubar-unselected-focused-label-color | — | |
--tanrei-comp-menubar-unselected-focused-state-layer-color | — | |
--tanrei-comp-menubar-unselected-hovered-container-color | — | |
--tanrei-comp-menubar-unselected-hovered-icon-color | — | |
--tanrei-comp-menubar-unselected-hovered-label-color | — | |
--tanrei-comp-menubar-unselected-hovered-state-layer-color | — | |
--tanrei-comp-menubar-unselected-icon-color | sys.color.on-surface | |
--tanrei-comp-menubar-unselected-label-color | sys.color.on-surface | |
--tanrei-comp-menubar-unselected-pressed-container-color | — | |
--tanrei-comp-menubar-unselected-pressed-icon-color | — | |
--tanrei-comp-menubar-unselected-pressed-label-color | — | |
--tanrei-comp-menubar-unselected-pressed-state-layer-color | — | |
--tanrei-comp-menubar-unselected-state-layer-color | comp.menubar.unselected.label.color | |
--tanrei-comp-menubar-label-typescale | sys.typescale.label | 500 14px/1.4286 |
测试样例
作为基准的渲染结果。所有平台都以相同的 ID 为每个样例截取快照,以便相互比较。
default
label: Application overflowLabel: More menus items: 7 open
label: Application overflowLabel: More menus items: 7 open-middle
label: Application overflowLabel: More menus items: 7 disabled-menu
label: Application overflowLabel: More menus items: 7 two-menus
label: Application overflowLabel: More menus items: 2 single
label: Application overflowLabel: More menus items: 1 short-titles
label: Application overflowLabel: More menus items: 3 many
label: Application overflowLabel: More menus items: 12 long-labels
label: Application overflowLabel: More menus items: 4 ja
label: アプリケーション overflowLabel: その他のメニュー items: 7 rtl
label: التطبيق overflowLabel: قوائم أخرى direction: rtl items: 7 rtl-open
label: التطبيق overflowLabel: قوائم أخرى direction: rtl items: 7 many-rtl
label: التطبيق overflowLabel: قوائم أخرى direction: rtl items: 12 ltr-titles-rtl
label: التطبيق overflowLabel: قوائم أخرى direction: rtl items: 5 mixed
label: Application overflowLabel: More items: 5 mixed-open
label: Application overflowLabel: More items: 5 link-new
label: Application overflowLabel: More items: 4 link-current
label: Application overflowLabel: More items: 5 disabled-item
label: Application overflowLabel: More items: 5 mixed-many
label: Application overflowLabel: More items: 14 mixed-rtl
label: التطبيق overflowLabel: المزيد direction: rtl items: 6 生成自 specs/menubar.spec.json, tokens/comp/menubar.tokens.json, design/tokens/
标题打开菜单,菜单栏的大部分都是标题。直接起作用的条目是例外:在菜单旁边放一两个,理由见直接起作用的条目。如果一个产品有许多命令需要一按即达,这些命令应当放在工具栏或应用栏里。
这一行只接受菜单和栏项,别的都不接受。Button、直接写进菜单栏的锚元素、字段或复选框,会占用菜单栏用来移动焦点的按键,而且它们也不是菜单项。这样的内容在行里不占位置,并会在调试构建中报告。
焦点到达栏项、方向键、Home、End、键入字母,以及指针从它上面经过,都不会激活它。只有按下、Enter 或 Space 才会。在栏项上,向下方向键和向上方向键什么也不做,也不会滚动页面:同样的按键不能在一个条目上打开菜单,到了下一个条目却滚动页面。
在菜单里,沿行方向的方向键会前往下一个条目。如果那是标题,它的菜单就打开。如果那是栏项,打开的菜单就关闭,焦点落在这个栏项上。什么也不会打开,什么也不会执行;从这里开始,方向键沿这一行移动而不打开菜单,直到你再次打开一个菜单。
在标题上按下会打开它的菜单。在已打开的标题上按下则关闭它。有一个菜单打开时,指针移到另一个标题上,会立即打开那个标题的菜单,没有延迟,也没有动效。没有菜单打开时,悬停就只是悬停:用户没有要求的东西,菜单栏一概不打开。
菜单打开时,指针移到栏项上,只会让它显示悬停。打开的菜单保持不动,所以指针从“文件”经过“运行”移向“视图”时,途中不会丢掉菜单。在栏项上按下,会先关闭菜单,再激活这个栏项。
某一项被激活时,它所在的菜单关闭,焦点回到这个菜单的标题上。作用于文档的命令,通常需要让焦点回到文档中。请在你的处理函数里移动焦点,菜单栏会让它留在你放的地方。
菜单会画出项的快捷键,并把它暴露给辅助技术。菜单和菜单栏都不监听这些按键。按键由你来绑定,因为只有产品知道,焦点所在的位置适用哪些快捷键。
菜单栏没有访问键:标题里没有带下划线的字母,也没有跳到菜单栏的按键。访问键因平台而异,在 Web 上会与浏览器自己的访问键冲突,而且用哪个字母要按语言逐一选定。焦点一旦位于菜单栏上,键入一个字母就会移到以它开头的下一个标题。要提供你自己的按键,可以设置某个菜单的 open,或者把焦点移到某个标题上。
标题保持自己的大小,绝不会被截断。这一行放不下所有条目时,菜单栏从末尾开始把条目整个折叠起来,只折叠必须折叠的那几个,收进一个溢出项,这个溢出项画成图标集的 more_horiz。在溢出项的菜单里,每个被折叠的菜单是一个子菜单,标签相同,项也相同;每个被折叠的栏项是一个项:操作成为菜单项,链接项成为地址相同的链接项。什么都放不下时,这一行就只有这一个溢出项。溢出项始终打开菜单,从不直接起作用。
在任何宽度下,按键、角色和顺序都相同,也不会丢失任何命令。被折叠的菜单,它的 open 和 toggle 的含义与在行里时相同。被折叠的栏项,它的 click 仍是同一个元素上的同一个事件:无论这个条目显示在哪里,你注册在栏项上或你的锚元素上的监听器都只会收到一次。条目被折叠时,监听器收到事件的那一刻,溢出项的菜单还开着,之后菜单才关闭。用 overflow-label 给溢出项一个名称,或者用 setWords({ menubarOverflowLabel: … }) 为整个产品只给一次。
在手机上要不要显示菜单栏,由你决定。在手机上,惯用方式是把命令放在应用栏的操作及其“更多”菜单里,产品不渲染菜单栏是合理的。之所以有折叠,是为了让已经渲染出来的菜单栏绝不丢失任何命令。
桌面系统位于窗口之外的菜单栏,以及平板电脑为硬件键盘提供的菜单栏,是由系统根据产品声明的命令绘制的。请用平台自己的 API 把你的命令也交给那个菜单栏:用户会到那里去找。Tanrei 的菜单栏是你窗口之内的那一个,按 Tanrei 的令牌绘制。它不填充系统的菜单栏,也不取代它。
- 应用的命令多到需要分成几个有名称的菜单时,用它来容纳这些命令。
- 用一两个词为每个菜单命名,说明其中的命令作用于什么。
- 某个菜单里的命令全都不适用时,让它留在菜单栏里,并把它禁用。用户仍然能听到它的存在。
- 在命令所在的项里显示它的快捷键,并自己绑定这些按键。
- 命令执行之后,把焦点移到它所作用的对象上。
- 不要把它用于网站的栏目或页面。
- 不要把 Button、裸链接、切换开关或字段放进这一行。操作或链接要作为栏项放进去。
- 不要做只有栏项的菜单栏,也不要把无法撤销的命令直接放在菜单栏上。
- 不要在指针只是经过标题时打开菜单。
- 不要把标题标记为选中。已打开的标题只在它的菜单显示期间带有标记。
- 不要让菜单栏的菜单与产品的其他菜单样式不同。它们就是菜单,使用菜单的令牌。
- 角色
menubar- 标注
- label names the bar; it is never drawn. Each title is a menu item named by its menu's label, which also names the menu it opens; it says that it opens a menu, whether that menu is open, whether it is disabled, and where it stands among the bar's titles. A bar item is a menu item named by its own label; it does not say that it opens a menu and has no expanded state, and says whether it is disabled and where it stands among the bar's entries, which are counted together. A link item is the platform's link exposed as a menu item (MBR-35); while its target is new its targetLabel is its description, and while it is current it says so. The overflow item is named by overflowLabel, and the menus folded into it keep their labels as its submenu items, the bar items folded into it as its items. Everything inside a menu is named as the menu's spec says. The overflow item's glyph is decorative.
- 最小目标
- 即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px
键盘操作
| 按键 | 动作 |
|---|---|
| Tab / Shift+Tab | Moves focus into the bar, to the entry focused last or else the first entry, and out of it: the bar is one Tab stop. With a menu open, closes it and moves focus on out of the bar. |
| Right Arrow / Left Arrow on a title / a bar item (mirrored in a right-to-left layout) | Moves focus to the next or previous entry of the row, wrapping at the ends. Disabled entries and the overflow item are reached too. Opens nothing and runs nothing. |
| Home / End on a title / a bar item | Moves focus to the first or last entry. |
| Printable character on a title / a bar item | Moves focus to the next entry whose label begins with it, whatever its letter case. Opens nothing and runs nothing. |
| Enter, Space / Down Arrow on a title | Opens its menu and moves focus to the menu's first enabled item. |
| Up Arrow on a title | Opens its menu and moves focus to the menu's last enabled item. |
| Enter / Space on a bar item | An action: runs it (click); focus stays on the bar item unless the product moved it. A link item: follows the link, Space as Enter does. With a modifier key, Enter on a link item is the platform's (another tab or window). |
| Down Arrow / Up Arrow on a bar item | Nothing: there is no menu to open. The keys are still taken, so the page does not scroll. |
| Down Arrow / Up Arrow, Home / End, printable characters in a menu | The menu's own keys, within the level that holds focus (MNU-7). |
| Right Arrow in a menu (Left Arrow in a right-to-left layout) | On a submenu item, opens its submenu. On any other item, at any level, closes the menu and moves to the next enabled entry: a title's menu is opened, with focus on its first enabled item; a bar item takes focus, and nothing opens or runs (MBR-40). |
| Left Arrow in a menu (Right Arrow in a right-to-left layout) | In a submenu, closes that submenu and returns focus to its item. In the menu's own surface, closes the menu and moves to the previous enabled entry: a title's menu is opened, with focus on its first enabled item; a bar item takes focus, and nothing opens or runs (MBR-40). |
| Enter / Space on an item of a menu | Activates it as the menu says; an action or a link item closes the menu and focus returns to the title. |
| Escape | Closes the innermost open submenu; with none open, closes the menu and returns focus to its title. Does nothing while no menu is open. |
备注
- What it is for: the commands of an application — an editor, a spreadsheet, a drawing or planning tool — in a window wide enough to show several named menus. It is not a site's main navigation with drop-downs: a row of links to pages, always in view, is navigation and belongs in a navigation landmark of links (the menu's own note on link items says the same). A menubar tells assistive technology that the arrow keys, and not Tab, move through everything in it and that every entry is a command; a person who meets that on a set of page links is misled.
- It follows the WAI-ARIA menubar pattern in full: one Tab stop, the horizontal arrow keys along the bar, the vertical ones into and inside a menu, the horizontal ones from inside a menu on to the neighbouring entry, Escape back to the title, type-ahead at both levels, and an entry of the bar that acts by itself — an action or a link. What it leaves out of that pattern: a checkbox or a radio entry directly in the bar. A choice that is switched needs a mark for its state, a title has no place for one, and the menus of the same bar already have it. Exploring the bar still runs nothing: focus arriving, the arrow keys, a typed character and a pointer over an entry never activate it; only a press, Enter or Space does (MBR-33).
- A bar item is for the one or two commands or places that belong beside the menus and are wanted in one press ("Run", "Share", "Documentation"). Nothing in its look tells it from a title that opens a menu, so its label says what it does at once: a verb for an action, the name of the place for a link. A command that deletes something or cannot be undone does not belong on the bar: a bar item has no destructive look and asks nothing before it runs; put it in a menu. A row of bar items with no menu is not a menubar — actions alone are a toolbar, links alone are navigation — and is reported (MBR-44).
- A link item is the platform's own link exposed as a menu item, exactly as the menu's own link item is and for the same reason (MNU-53): a menubar is read by its entries — how many, which one, the arrow keys — and that reading holds only while everything in the row is a menu item; so the role stays, and what makes the entry a link stays underneath it: its address, the browser's context menu and new-tab gestures, a crawler that follows it. Like the menu's, it is a link that Space activates. On the web it is the product's own anchor in the document, so it is a working link before the bar runs and where script does not (MBR-45). current marks the page the person is on among the bar's link items; the product sets it and the bar draws it with the fill a menu draws for the row the person is on, the same in both: it is where the person is, not a menu that is shown, so it never takes the expanded condition's colors.
- A link item that opens a new context is marked by the one rule every link form follows: the glyph open_in_new stands at the control's trailing glyph place, where the control lays out its own content and has such a place. A title's container has one — the overflow item's glyph stands in it — as a button's label has (BTN-24) and a menu's row has in its trailing icon column (MNU-55), which is where a folded link item's mark is drawn; the navigation bar draws none because its cell, an icon over a label, has no such place (NVB-32). Whether a mark is drawn or not, targetLabel says it (MBR-37).
- A bar item's click and the closing of an open menu come in one of two orders, by where the bar item is activated (MBR-33, MBR-36, MBR-43). In the row, the menu that is open belongs to another entry and is closed first, as it is when another title is pressed (MBR-10): the product's handler runs with no menu shown. Folded, the bar item is an item of the overflow item's menu, and its click comes first and the menu closes after it, as for every item of a menu (MNU-6, MBR-15). What a product can rely on in both: one click, on the bar item's own element, before the bar decides where focus rests.
- A bar's menu is to the bar what a submenu item is to a menu: a label, an open value of its own, a disabled value, and a toggle event. The surface it opens is a Tanrei Menu with the menu's own item kinds and tokens. The item kind menu of this spec has no items of its own in the schema because they are another spec's kinds; a fixture therefore draws an open menu empty, as a button with menu's fixtures do, and the tests fill it.
- Focus: the bar is part of the page and a Tab stop like a toolbar. A menu it opens takes focus and returns it to its title when it closes by Escape or by an item; Tab leaves the bar altogether. A bar item opens nothing, so it takes no focus away: after it runs, focus is still on it (MBR-33). After a command the product may move focus to what the command acts on (the document, a dialog it opened) in its handler, and the bar does not take it back (MBR-15).
- Disabled titles stay reachable along the bar with no menu open, so that a screen reader user hears that the menu exists and is unavailable; with a menu open, the horizontal arrow keys pass over them, because those keys then mean 'show me the next menu' and a disabled title has none to show (MBR-13). A disabled bar item is reached and passed over the same way. An enabled bar item is not passed over: the keys land on it, the open menu closes, and from there they go on along the bar without opening anything, because a menu follows focus only while one is shown (MBR-40).
- Keyboard shortcuts shown in the menus are drawn and exposed by the menu through the keyboard shortcut component; the bar binds none (MBR-16). Access keys — an underlined letter per title, a key that jumps to the bar — are left out: they differ by platform, collide with the browser's own on the web, and a letter is chosen per language, which a component that ships no words cannot do. Type-ahead on the bar gives the first-letter jump once focus is there.
- A compact window: the bar folds what does not fit into one overflow item at its end, whose menu holds the folded menus as submenus and the folded bar items as items (MBR-43); when nothing fits it is that one item alone (MBR-21, MBR-22). The keys, the roles and the order are the same at every width, and no command is lost. A product that would rather not show a menubar at all on a phone — commands in an app bar's actions and its 'more' menu are the touch idiom — does not render it there; that is the product's layout and needs nothing from the bar.
- Where the platform has a menu bar of its own outside the window (a desktop system's global bar, a tablet's bar for a hardware keyboard), the product should give its commands to that bar with the platform's own API as well: people look for them there, and its keys are the platform's. Tanrei's menubar is drawn inside the product's window from Tanrei's tokens; it does not fill or replace the system's.
- Contrast: a title's or a bar item's label keeps at least 4.5:1 on the bar in every state of both conditions. The open title's fill supports the open menu beneath it, which is what says a menu is open; the fill is not held to a contrast of its own. The focus ring is the system's and keeps 3:1 against what it is drawn on.
- minimumTarget: a title is 40 high in a bar 48 high in the default system, so its touch target lies inside the bar; a short title ('Go') is narrower than the target and gets the room MBR-17 gives it.
生成自 specs/menubar.spec.json, tokens/sys/measure.tokens.json
- 菜单栏以菜单栏角色暴露,由
label命名,方向为水平。每个标题是一个会打开菜单的菜单项,以它的菜单的标签命名,并带有这个菜单是否打开、是否禁用,以及它在各条目中的位置。打开的菜单是一个以其标题命名的菜单。 - 栏项是一个以其标签命名的菜单项。它不会说自己会打开菜单,也没有展开状态。链接项是你的锚元素,带的是菜单项角色,而不是链接角色:辅助技术逐项朗读菜单栏,而菜单栏角色只有在每个条目都是菜单项时才有效。它把自己的
target-label作为描述朗读,并说明它是否是当前页面。 - 整个菜单栏在焦点顺序中是一个停靠点:停在最近一次获得焦点、被激活或打开过菜单的那个条目上,没有这样的条目时停在第一个条目上。链接项绝不会自成一个 Tab 停靠点。
- 在标题或栏项上:沿行方向的方向键移到下一个和上一个条目,到头时循环;Home 和 End 移到第一个和最后一个;键入字符会移到以它开头的下一个条目。这些按键都不会打开菜单,也不会执行任何操作。在标题上,Enter、Space 和向下方向键打开菜单,焦点落在其中第一个启用的项上;向上方向键也打开菜单,焦点落在其中最后一个启用的项上。在栏项上,Enter 和 Space 激活它,向下方向键和向上方向键什么也不做。
- 在菜单里:菜单自己的按键照常可用。指向行末方向的方向键,在子菜单项上打开子菜单;在任何深度的其他项上,则前往下一个启用的条目:是标题就打开它的菜单,是栏项就让它获得焦点。指向行首方向的方向键关闭子菜单,在菜单自己的表面中则前往上一个条目。Escape 关闭最内层打开的子菜单;没有子菜单打开时,它关闭菜单,并把焦点交还给这个菜单的标题。Tab 关闭菜单并离开菜单栏。
- 沿菜单栏移动时可以到达禁用的标题,这样屏幕阅读器会说出这个菜单存在但不可用。有菜单打开时,同样的按键会跳过它们。
- 键盘焦点会在标题周围显示焦点环。通过指针获得的焦点不会。
- 标题的点击区域达到最小目标,而画出来的样子不变。比目标更窄的标题,在行里仍占据目标的长度。
- 打开的菜单不是模态的。焦点不会被限制在其中,没有任何内容变为惰性(inert),在外部的按下也会传到它下方的内容。
- 在从右到左的布局中,标题从右侧排起,这一行在左端折叠,每个菜单打开时右边缘与标题的右边缘对齐,左方向键表示“下一个”。标题的标签保持它自己的方向。
- 文本放大时,每个标题随标签一起变大,这一行会把放不下的条目折叠起来。在宽度为 320、文本为 200% 时,每个菜单仍然可以到达,也没有任何内容需要横向滚动。
- 在强制颜色模式下,标签使用按钮文本颜色,已打开的标题使用高亮颜色,悬停的标题周围有一圈线。链接项使用用户所选的链接颜色,禁用的条目使用禁用文本颜色。当前链接项的填充会被强制颜色模式去掉,因此它在边缘内侧得到一圈链接颜色的线。
- 调试构建会报告:没有标签的菜单栏、菜单或栏项,没有项的菜单,菜单栏里既不是菜单也不是栏项的任何内容,只有栏项的菜单栏,在新标签页中打开却没有说明这一点的链接项,以及没有名称的溢出项。
- 用页面的语言给菜单栏一个
label(“应用菜单”)。它绝不会被画出来。 - 给每个菜单一个
label,给每个操作一个label,给每个链接项的锚元素写上文本。 - 给在新标签页中打开的链接项一个
target-label,并在用户当前所在页面对应的链接项上设置aria-current="page"。 - 用
overflow-label或产品的文字给溢出项一个名称。组件不提供任何文字。 - 绑定你的项所显示的快捷键。
- 系统有自己的菜单栏时,也向它声明你的命令。
- 不要把菜单栏用于导航。