メインコンテンツへスキップ
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
はじめにWebReactSwiftUIJetpack ComposeFlutterReact Nativeプリセット自分のデザインシステムを作るAI エージェントと Tanrei を使うブランドデザイントークンデザインシステムとカスタマイズTheme Studioカラータイポグラフィアイコン形状エレベーションモーションインタラクション状態振る舞いレイアウトアクセシビリティ言葉カラートークンタイプスケールトークン形状トークンエレベーショントークンモーショントークン状態トークンスペースと寸法のトークンコンポーネントボタンメインアクションボタンセグメントボタンボタングループメニュー付きボタンツールバーコピーボタンチェックボックスラジオボタンスイッチチップメニュースライダースライダーフィールドカレンダーDateTimePickerフィールドグループラジオグループチェックボックスグループセレクトセレクトフィールドメニューバーファイルアップロードファイルピッカー 上級者向けドロップゾーンテキスト入力欄 上級者向けテキストフィールドチップフィールドフィールドコンボボックス 上級者向けコンボボックスフィールド数値入力欄 上級者向け数値フィールドカラー入力欄 上級者向けカラーフィールドワンタイムコード入力欄 上級者向けワンタイムコードフィールド埋め込みテキスト入力欄 上級者向けチャットコンポーザーカード区切り線モーダルレイヤー 上級者向けダイアログリストリスト行ボトムシートサイドシートカルーセルコードブロックテーブルポップオーバーステップアコーディオン説明リスト図アプリシェルページヘッダーツリービュータイムライン画像プレビューハンドルサイズ変更できる枠スクロールエリアバッジプログレスインジケーターメーターナンバーティッカーローディングインジケータースナックバーツールチップキーボードショートカットタグインラインコードコールアウトアバター空の状態タググループスケルトンファイル項目色見本マーキーテキストタブタブビューアプリバーナビゲーションバーナビゲーションドロワー検索リンクページネーション前後のリンクステッパーパンくずリスト目次リンクカードFAB メニューステータスTanrei の作り方コントリビューションライセンスと帰属
ホームコンポーネントメニュー

メニュー

呼び出した要素から開く一時的なサーフェスに、少数のアクションや選択肢を並べて見せます。

content_copyMarkdown で表示
カテゴリ
選択
トークン
comp.menu
レベル
基礎

概要

Web
tanrei-menu
schedule
SwiftUI
TanreiMenu
schedule
Jetpack Compose
TanreiMenu
schedule
Flutter
TanreiMenuAnchor
schedule
React Native
Menu
schedule

生成元 docs/status/status.json, specs/menu.spec.json

アクション、チェックボックス項目、ラジオのグループ、破壊的なアクション

サブメニュー:隣にもう 1 つの階層を開く項目

リンクのメニュー:どの行も行き先を持ち、1 つは今いるページで、1 つは新しいタブで開く

使い方

メニューを開く起点(アンカー)は、利用者側のボタン、アイコンボタン、入力欄です。メニューはアンカーを描きません。アンカーの位置を読んでそこにサーフェスを配置し、閉じたらフォーカスをアンカーへ戻します。開くのは利用者側で、閉じるのはメニューから求められたときです。

メニューの項目には 5 つの種類があります。アクションの item、checkbox-item、radio-item、隣にメニューのもう 1 つの階層を開く submenu、そしてほかの種類の項目をまとめる group です。項目の渡し方は、各プラットフォームで繰り返しの子要素を渡すときの流儀に従います。Web では子要素、SwiftUI ではビュービルダーの中のビュー、Compose ではコンテンツラムダの中の呼び出し、Flutter では menuChildren のウィジェット、React Native では kind で種類を見分けるプレーンなオブジェクトの配列 items です。チェックボックス項目の checked はその項目自身のものです。どのラジオ項目がチェックされているかは、値によってグループが持ちます。

項目のキーボードショートカットは shortcut プロパティで指定します。書き方はキーボードショートカットの中立なキー名(mod+c)です。メニューはそれを項目の末尾にキーボードショートカットのコンポーネントで描き、Apple のキーボードでは ⌘C、それ以外では Ctrl+C と表示して、項目のキーボードショートカットとして伝えます。キーを実際に効かせるのはプロダクト側です。trailingText は、件数や「Beta」のようなショートカット以外のもののためのものです。Web はこのすべてに対応し、現在の見た目で描きます。SwiftUI、Compose、Flutter、React Native は追従待ちです。追従するまでは shortcut プロパティがなく、以下の例のようにショートカットを trailingText に書きます。サブメニュー、separation、iconSpace、markIcon もありません。また、チェックを 16 の枠に独自の印で描き、チェックされた行を塗らず、グループのタイトルをラベルの位置から始めます。

href を持つ項目はリンク項目です。プラットフォーム自身のリンクで、アクションとまったく同じに描かれ、同じキーで移動できます。通常の操作では、項目の click を発火し、メニューを閉じ、そのあとで行き先を開きます。target と targetLabel はボタンのものと同じです。current は、利用者が今いるページのリンク項目を示します。専用の塗りで描き、チェックは付けません。リンク項目があるのは Web です。SwiftUI、Compose、Flutter、React Native にはまだありません。

メニューは、ある領域のコンテキストメニューにもなります。trigger を context にすると、アンカーはその領域(キャンバス、リスト、行など)になります。利用者は、右クリック、長押し、またはフォーカスのある要素での Shift+F10 か ContextMenu キーで、領域の中のある位置にメニューを求めます。メニューはその位置を添えて contextRequest を発火し、プロダクトがそこで開きます。コンテキストトリガーがあるのは Web です。SwiftUI、Compose、Flutter、React Native にはまだありません。

<tanrei-button variant="text" id="more" label="その他"><span slot="icon">more_vert</span></tanrei-button>

<!-- anchor には、メニューを開く要素の id を指定します。 -->
<tanrei-menu anchor="more">
  <!-- shortcut は中立なキー名で書き、利用者のキーボードに合わせて ⌘C か Ctrl+C と描きます。 -->
  <tanrei-menu-item label="コピー" leading-icon="content_copy" shortcut="mod+c"></tanrei-menu-item>
  <!-- グループのタイトルは、<optgroup label> と同じく label 属性です。 -->
  <tanrei-menu-group label="表示">
    <tanrei-menu-checkbox-item label="グリッドを表示" checked></tanrei-menu-checkbox-item>
  </tanrei-menu-group>
  <!-- グループか個々の項目に mark-icon="check" を付けると、チェックされた項目をただのチェックで示します。 -->
  <tanrei-menu-group label="並べ替え" value="name" mark-icon="check">
    <tanrei-menu-radio-item label="名前" value="name"></tanrei-menu-radio-item>
    <tanrei-menu-radio-item label="更新日" value="date"></tanrei-menu-radio-item>
  </tanrei-menu-group>
  <!-- サブメニュー:子要素が次の階層です。toggle を発火し、close は発火しません。 -->
  <tanrei-submenu label="最近使った項目を開く" leading-icon="history">
    <tanrei-menu-item label="Report.pdf"></tanrei-menu-item>
    <tanrei-menu-item label="Notes.txt"></tanrei-menu-item>
  </tanrei-submenu>
  <tanrei-menu-item label="削除" leading-icon="delete" destructive></tanrei-menu-item>
  <!-- href があれば項目はリンクです。click を発火し、メニューを閉じ、そのあとブラウザーがリンクをたどります。 -->
  <tanrei-menu-item label="キーボードショートカット" href="/help/shortcuts/"></tanrei-menu-item>
  <tanrei-menu-item label="ソース" href="https://example.com/tanrei" target="new" target-label="新しいタブで開きます"></tanrei-menu-item>
</tanrei-menu>

<!-- separation="gap" は、デザインシステムの選択にかかわらず、このメニューとそのサブメニューでグループを余白で分けます。 -->
<tanrei-menu anchor="more" separation="gap">…</tanrei-menu>

<!-- trigger="context": anchor の id を持つ領域のコンテキストメニュー。aria-label で名前を付けます。 -->
<div id="canvas">…</div>
<tanrei-menu id="canvas-menu" anchor="canvas" trigger="context" aria-label="キャンバス">…</tanrei-menu>

<script type="module">
  import '@tanrei/web/components/menu';
  const menu = document.querySelector('tanrei-menu');
  // アンカーの押下はアンカー自身のものです。メニューは閉じないので、ここで開閉を切り替えます。
  document.querySelector('#more').addEventListener('click', () => (menu.open = !menu.open));
  // close は理由(escape、outside、tab、focus-out、item)を伝えます。
  menu.addEventListener('close', () => (menu.open = false));
  // click、change、toggle は項目かグループで発火してバブリングするので、メニューに付けた 1 つのリスナーですべて受け取れます。
  menu.querySelector('[label="コピー"]').addEventListener('click', copy);
  menu.querySelector('[label="グリッドを表示"]').addEventListener('change', (e) => setGrid(e.detail.checked));
  menu.querySelector('[label="並べ替え"]').addEventListener('change', (e) => sortBy(e.detail.value));

  // 領域での右クリック、長押し、Shift+F10、ContextMenu キーがコンテキストメニューを求めます。
  // detail には位置、求められた対象の要素、求め方が入ります。開くか、イベントを取り消して
  // ブラウザー自身のメニューに任せます。
  const canvasMenu = document.querySelector('#canvas-menu');
  canvasMenu.addEventListener('contextrequest', () => (canvasMenu.open = true));
  canvasMenu.addEventListener('close', () => (canvasMenu.open = false));
</script>
import { Button, Menu, MenuCheckboxItem, MenuGroup, MenuItem, MenuRadioItem, Submenu } from '@tanrei/react';

<Button variant="text" id="more" label="その他" onClick={() => setOpen(!open)}><span slot="icon">more_vert</span></Button>
<Menu anchor="more" open={open} onClose={() => setOpen(false)}>
  <MenuItem label="コピー" leadingIcon="content_copy" shortcut={['mod+c']} onClick={copy} />
  <MenuGroup label="表示">
    <MenuCheckboxItem label="グリッドを表示" checked={grid} onChange={(e) => setGrid(e.detail.checked)} />
  </MenuGroup>
  <MenuGroup label="並べ替え" value={sort} onChange={(e) => setSort(e.detail.value)}>
    <MenuRadioItem label="名前" value="name" />
    <MenuRadioItem label="更新日" value="date" />
  </MenuGroup>
  <Submenu label="最近使った項目を開く" leadingIcon="history">
    <MenuItem label="Report.pdf" onClick={() => openFile('Report.pdf')} />
    <MenuItem label="Notes.txt" onClick={() => openFile('Notes.txt')} />
  </Submenu>
  <MenuItem label="削除" leadingIcon="delete" destructive onClick={remove} />
  {/* リンク項目。ルーターは click をキャンセルして自分で移動します。メニューはそれでも閉じます。 */}
  <MenuItem label="キーボードショートカット" href="/help/shortcuts/" onClick={(e) => { e.preventDefault(); navigate('/help/shortcuts/'); }} />
</Menu>

{/* id が canvas の領域のコンテキストメニュー。求められたら開きます。 */}
<Menu anchor="canvas" trigger="context" aria-label="キャンバス" open={contextOpen} onContextRequest={() => setContextOpen(true)} onClose={() => setContextOpen(false)}>
  <MenuItem label="貼り付け" onClick={paste} />
</Menu>
import Tanrei

// .popover と同じく、アンカーに付けるモディファイアーです。閉じると isPresented に false が書き込まれます。
TanreiIconButton("その他", action: { showMenu = true }) { Image(systemName: "ellipsis") }
    .tanreiMenu(isPresented: $showMenu, placement: .bottomStart) {
        TanreiMenuItem("コピー", action: copy)
        TanreiMenuSection("表示") {
            TanreiMenuToggle("グリッドを表示", isOn: $showGrid)
        }
        TanreiMenuSection("並べ替え", selection: $sort) {
            // .tag(_:) は TanreiMenuOption そのものに付けます。
            TanreiMenuOption("名前").tag("name")
            TanreiMenuOption("更新日").tag("date")
        }
        TanreiMenuItem("削除", role: .destructive, action: delete)
    }
import io.github.chnotchy.tanrei.components.TanreiCheckboxMenuItem
import io.github.chnotchy.tanrei.components.TanreiIconButton
import io.github.chnotchy.tanrei.components.TanreiMenu
import io.github.chnotchy.tanrei.components.TanreiMenuGroup
import io.github.chnotchy.tanrei.components.TanreiMenuItem
import io.github.chnotchy.tanrei.components.TanreiRadioMenuItem
import io.github.chnotchy.tanrei.primitives.TanreiText

// DropdownMenu と同じく、アンカーの Box の中で呼びます。閉じるのは呼び出し側で、onDismissRequest に理由が届きます。
Box {
    TanreiIconButton(onClick = { expanded = true }, contentDescription = "その他") { TanreiText("more_vert") }
    TanreiMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
        TanreiMenuItem(text = "コピー", onClick = ::copy, trailingText = "⌘C", leadingIcon = { TanreiText("content_copy") })
        TanreiMenuGroup(title = "表示") {
            TanreiCheckboxMenuItem(text = "グリッドを表示", checked = showGrid, onCheckedChange = { showGrid = it })
        }
        TanreiMenuGroup(title = "並べ替え", selectedValue = sort, onSelectedValueChange = { sort = it }) {
            TanreiRadioMenuItem(text = "名前", value = "name")
            TanreiRadioMenuItem(text = "更新日", value = "date")
        }
        TanreiMenuItem(text = "削除", onClick = ::delete, destructive = true, leadingIcon = { TanreiText("delete") })
    }
}
import 'package:tanrei/tanrei.dart';

// MenuAnchor と同じく、コントローラーで開きます。閉じるよう求めるときは、理由を添えて onClose を呼び、自分で閉じます。
final TanreiMenuController menu = TanreiMenuController();

TanreiMenuAnchor(
  controller: menu,
  onClose: (TanreiMenuCloseReason reason) {},
  menuChildren: <Widget>[
    TanreiMenuItem(
      onPressed: copy,
      leadingIcon: const Icon(AppSymbols.contentCopy),
      trailingText: '⌘C',
      child: const Text('コピー'),
    ),
    TanreiMenuGroup<String>(
      title: '表示',
      children: <Widget>[
        TanreiCheckboxMenuItem(
          value: showGrid,
          onChanged: (bool value) => setState(() => showGrid = value),
          child: const Text('グリッドを表示'),
        ),
      ],
    ),
    TanreiMenuGroup<String>(
      title: '並べ替え',
      groupValue: sort,
      onChanged: (String value) => setState(() => sort = value),
      children: const <Widget>[
        TanreiRadioMenuItem<String>(value: 'name', child: Text('名前')),
        TanreiRadioMenuItem<String>(value: 'date', child: Text('更新日')),
      ],
    ),
    // onPressed を null にすると項目は無効になります。
    TanreiMenuItem(onPressed: delete, destructive: true, child: const Text('削除')),
  ],
  builder: (BuildContext context, TanreiMenuController controller, Widget? child) =>
      TanreiIconButton(onPressed: controller.open, icon: const Icon(AppSymbols.moreVert), semanticLabel: 'その他'),
);
import { useRef, useState } from 'react';
import type { View } from 'react-native';
import { IconButton, Menu } from '@tanrei/react-native';

const more = useRef<View>(null);
const [open, setOpen] = useState(false);

// メニューが開いているかどうかは、アンカー側で支援技術に伝えます。メニューからは設定できません。
<IconButton ref={more} accessibilityLabel="その他" accessibilityState={{ expanded: open }} onPress={() => setOpen(true)}>
  more_vert
</IconButton>
// 制御コンポーネントです。onDismiss に理由が届いたら、visible を false にします。
<Menu
  visible={open}
  anchorRef={more}
  accessibilityLabel="その他"
  onDismiss={() => setOpen(false)}
  items={[
    { label: 'コピー', leadingIcon: 'content_copy', trailingText: '⌘C', onPress: copy },
    { kind: 'group', title: '表示', items: [{ kind: 'checkbox-item', label: 'グリッドを表示', checked: grid, onCheckedChange: setGrid }] },
    { kind: 'group', title: '並べ替え', value: sort, onValueChange: setSort, items: [
      { kind: 'radio-item', label: '名前', value: 'name' },
      { kind: 'radio-item', label: '更新日', value: 'date' },
    ] },
    { label: '削除', leadingIcon: 'delete', destructive: true, onPress: remove },
  ]}
/>

仕様

補足テキスト、末尾アイコン、無効な項目

チェックとアイコンの列の上のグループタイトル

印とアイコンの列:iconSpace の auto(既定)と never

印:既定のシステムの箱と輪、そして mark-icon で指定したただのチェック

分け方:グループの間の区切り線と、余白

既定のシステムでは、チェックボックス項目はチェックボックスとして、ラジオ項目はラジオボタンとして印を描きます。チェックされていてもいなくても描くので、空の箱とチェックの入った塗られた箱、細い輪と中を塗った太い輪になります。そのため、オフの選択肢も選択肢であることが見て分かります。これらは印の列に描く絵であって、コントロールではありません。押すのもフォーカスするのも、行だけです。箱の中のチェックは、チェックボックスと同じく、コントロール自身の印のための太いウェイトで描きます。

markIcon は、チェックされた項目を示すグリフをアイコンセットの名前で指定し、既定の印の代わりにします。個々の項目にも、グループに付けてその中のすべての項目にも指定できます。check を指定すると、隣の先頭アイコンと同じ大きさ(既定のシステムでは 24)のただのチェックを描き、チェックされていない間は何も描きません。印はデザインシステムが選ぶもので、m3-like と cupertino はただのチェックを使います。右から左のレイアウトでもチェックは反転しません。メニューが開いている間に項目のチェックが変わると、印は動かずに切り替わり、動きを減らす設定ではすぐに切り替わります。

チェックされた行は、マークで示します。既定のシステムでは、標準コントラストではチェックされた行を塗りません。そのため、操作していないメニューで塗られている行は、利用者が今いるページの行だけです。より強い手がかりを求める中コントラストと高コントラストでは、チェックされた行も塗ります。行自身の内容の色をメニューのサーフェスの上に薄く重ねた層で、ライトテーマでもダークテーマでも、ホバーした行より一段強く見えます。そのため、浮き上がったサーフェスの上でも、チェックされた行、ホバーした行、何もしていない行を見分けられます。メニューは、描いているスキームのトークンから塗りを読みます。コントラストのスキームごとに塗りを決めるのはデザインシステムです。サブメニューを開いている項目は、開いている間ホバーの塗りを保ち、いま使っている階層までの道筋を示します。塗られた行のように見えることはありません。

グループのタイトルは、行の内容が始まる位置から始まります。メニューがチェックや先頭アイコンの列を描くときはその列の上から、どちらも描かないときはラベルの上からです。反対側の端からも同じ距離で終わり、折り返し、文字を大きくすると大きくなり、右から左のレイアウトでは反転します。

構成要素

構成要素

Web パッケージが描くフィクスチャ actions の上に描いています。
パーツ 説明
1 surface The floating sheet that holds the items: its color, corners, elevation and padding. It is placed against the anchor, or at the point a context menu was asked for, and drawn above everything else.
2 container One item's row. It carries hover, focus and press, the fill of a checked item, and the fill of a submenu item whose submenu is open.
3 mark · 任意 What shows whether a checkbox or radio item is chosen, in the mark column at the start of the row. By default it is the look of the matching control, drawn as a mark: a box for a checkbox item (empty, or filled with the icon set's check) and a ring for a radio item (thin, or heavy with its fill), in both conditions. Where the design system chooses the check (comp.menu.checkbox-item.mark, comp.menu.radio-item.mark) or the item or its group names a glyph (markIcon), it is that glyph of the system's icon set while the item is checked and nothing while it is not. It is never a control of its own: it takes no focus and no press, and the item stays a menu item checkbox or a menu item radio. Its column is kept, empty, in the other rows of its section while iconSpace is auto, in every row while it is always, and in its own row only while it is never (MNU-59).
4 leading-icon · 任意 An icon before the label. Its column is kept, empty, in the other rows of its section while iconSpace is auto, in every row while it is always, and in its own row only while it is never (MNU-59).
5 label The item's text and its accessible name.
6 supporting-text · 任意 A line below the label that says more about the item.
7 trailing-text · 任意 Text at the end of the row that is not a keyboard shortcut, such as a count or "Beta". It shares the trailing column with the shortcut.
8 shortcut · 任意 The item's keyboard shortcut at the end of the row, in the trailing column: the keyboard shortcut component at its small size, in the form comp.menu.shortcut.variant names, its legends in the trailing text's style and color (MNU-31).
9 trailing-icon · 任意 An icon at the end of the row. A submenu item draws its submenu indicator in this column instead, and a link item that opens a new context draws its mark there, the glyph open_in_new, while the product names no glyph for it (MNU-55).
10 submenu-indicator · 任意 The glyph chevron_right of the system's icon set at the end of a submenu item's row, in the trailing icon column: it says the item opens another level, and points to the side that level opens on. The icon set marks it as mirrored, so the icon helper turns it in a right-to-left layout. The menu draws no chevron of its own.
11 submenu · 任意 The surface a submenu item opens: a menu of its own beside the surface that holds the item, drawn with the surface's tokens and holding items of every kind, further submenus included.
12 group · 任意 A run of related items (a group item), optionally under a title. The menu separates a group from what is next to it by a divider, or under gap by drawing it as a tile of its own, as its separation names, and a group of radio items is one set of choices.
13 group-title · 任意 The heading drawn above a group's items when the group has a title. It starts where the rows' content starts, not after the check and icon columns. It names the group and takes no focus.
14 divider · 任意 The hairline the menu draws between two groups while its separation is divider. Under gap there is none: each group is a tile of its own, and the space between the tiles parts them (MNU-36).
15 focus-ring Gap-then-ring indicator shown for keyboard focus, drawn inside the item's row.

寸法

寸法

長さ トークン 値(px)
surface padding-inline tanrei.comp.menu.surface.padding-inline 4
Web パッケージが描くフィクスチャ actions の上に描いています。

プロパティ

プロパティ 型 既定値 説明
open boolean false Whether the menu is shown. The product opens it from its anchor (a button, an icon-only Button, a field), or on contextRequest while trigger is context. Where open is a plain value (web, Compose, React Native) the product closes the menu by setting it to false when the menu emits close; where it is a binding or a controller (SwiftUI, Flutter) the menu closes itself through it (MNU-5).
placement bottom-start | bottom-end | top-start | top-end "bottom-start" Where the surface goes against the anchor: below or above it, with its start or end edge aligned to the anchor's. The menu flips to the other side when this side has no room. For a context menu opened at a point it is where the surface goes from the point: bottom-start puts its block-start, inline-start corner there (MNU-61).
trigger anchor | context "anchor" What the menu opens from. anchor: the product opens it from its anchor — a button, an icon-only Button, a field — and the surface is placed against the anchor's edges. context: the menu is the context menu of an area, and the anchor is that area (a canvas, a list, a row, a document's body of text); a person asks for the menu at a point in it — a secondary click, a long press of a touch or a pen, Shift+F10 or the ContextMenu key on the element of the area that has focus — and the menu emits contextRequest, the product sets open, and the surface opens at that point, or against that element for the keyboard. The items, the keys, the submenus and the close reasons are the same in both (MNU-60 to MNU-63).
separation auto | divider | gap "auto" How a group is told from what is next to it. divider: a line between them. gap: empty space and no line. Auto takes comp.menu.separation, the design system's answer (divider in the default system). It applies to the menu and to every submenu of it.
iconSpace auto | always | never "auto" Whether a row keeps the room of the mark column and of the leading icon column where it has nothing to draw there, so that the labels of a section start on one line — a section being a group, or a run of adjacent items outside any group. auto: a row keeps a column's room while another item of its section draws in that column (a checkbox or radio item its mark, an item its leading icon), so that a section without marks or icons draws its labels where the row's content starts. always: every row keeps both. never: only a row that draws in a column has it. The tree view's and the navigation drawer's iconSpace, with the same values and default. It applies to the menu and to every submenu of it (MNU-59).
density regular | compact "regular" How much room a row takes: its least height, its paddings, the gap between its columns and the size of its glyphs (the mark, the leading and the trailing icon), and never its text style. regular (a 48 row with 24 glyphs in the default system) is the menu of a product used by touch as well as by pointer. compact (a 32 row with 20 glyphs, the tree view's compact row) is for a tool used with a pointer, whose menus are long and opened often. For touch a row is never lower than the touch target in either (MNU-12). It applies to the menu and to every submenu of it (MNU-64). A design system may add values.

イベント

イベント 説明
close The menu asks to be closed, every open submenu with it: Escape or the platform's back while no submenu is open, a press outside, Tab, focus leaving it, an item's activation at any level, or its anchor no longer drawn (MNU-65). Carries which of these it was, named escape, back, outside, tab, focus-out, item or anchor-hidden (in the platform's casing). Closing a submenu alone is the submenu item's toggle, not this event.
contextRequest While trigger is context: the person asked for the context menu in the anchor's area. Carries the point in the viewport's coordinates, the element it was asked about (the one under the pointer, or the one with focus) and how it was asked: pointer (a secondary click, or the context-menu command of assistive technology), long-press (a touch or a pen held still) or keyboard (Shift+F10, the ContextMenu key). Set open to true to show the menu there. Cancelling it leaves the request to the platform: its own context menu answers it (MNU-60).

アイテム

このコンポーネントが繰り返すアイテムの種類です。フィクスチャはアイテムを順に並べ、各プラットフォームはそれぞれの流儀で種類ごとに名前を付けます。

item

An action: it does something and closes the menu. With an href it is a link: it goes somewhere, and the menu closes.

プロパティ 型 既定値 説明
label string "" The item's text and its accessible name. Required.
supportingText string "" A line below the label that says more about the item.
leadingIcon string "" The name of an icon (a glyph of the system's icon set) before the label.
trailingIcon string "" The name of an icon at the end of the row.
trailingText string "" Text at the end of the row that is not a keyboard shortcut, such as a count or "Beta". An item's shortcut goes in shortcut; an item that has both draws the shortcut.
shortcut strings The item's keyboard shortcut, in the keyboard shortcut component's key names: one chord per string, pressed in order (["mod+c"]). The menu draws it at the end of the row for the keyboard the person uses and exposes it as the item's keyboard shortcut (MNU-24, MNU-31). It does not make the keys work; the product binds them.
destructive boolean false Marks an action that deletes or discards something; it is drawn in the error color.
disabled boolean false Prevents activation. The item stays reachable with the arrow keys.
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 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 moved to with the same keys; a plain activation emits click, closes the menu and opens the destination (MNU-53, MNU-54). Empty, the item is an action and the product acts on click. Only the item kind has one: a checkbox item, a radio item and a submenu item take no destination (MNU-57).
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 item marks with the glyph open_in_new in its trailing icon column, unless trailingIcon names one, and says through targetLabel (MNU-55). 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 Marks a link item as the destination the person is at now (the current page): exposed as the current item and drawn with the fill of the row the person is on, comp.menu.current.container.color, in every contrast scheme, with no mark and no change of weight; it stays a link (MNU-58). The product sets it; the menu never does. Ignored without href.
イベント 説明
click The item was activated by pointer, touch, keyboard or assistive technology. A link item emits it before the menu closes and before its destination opens; a product that takes the navigation itself (a client-side router) cancels it, and the menu still closes (MNU-54).
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tanrei-menu-item TanreiMenuItem TanreiMenuItem TanreiMenuItem MenuItem
label · プロパティ label title text child label
supportingText · プロパティ supportingText supportingText supportingText supportingText supportingText
leadingIcon · プロパティ leadingIcon leadingIcon leadingIcon leadingIcon leadingIcon
trailingIcon · プロパティ trailingIcon trailingIcon trailingIcon trailingIcon trailingIcon
trailingText · プロパティ trailingText trailingText trailingText trailingText trailingText
shortcut · プロパティ shortcut shortcut shortcut shortcut shortcut
destructive · プロパティ destructive role destructive destructive destructive
disabled · プロパティ disabled disabled enabled onPressed 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
備考 With href the item's shadow root holds an <a role="menuitem" tabindex="-1"> in place of its row element, drawn through the shared link primitive (packages/web/src/primitives/link.ts) as <tanrei-list-item>'s is, with Space left among the keys that press it (MNU-54). target is the target attribute with the values same and new (the HTML spellings _self and _blank are read as those two), targetLabel the target-label attribute, and rel (to which new adds noopener) and download are web-only and forwarded to the anchor. click is the anchor's own click: cancelling it (preventDefault()) cancels the navigation and not the menu's close. The menu fires close from that click only when event.button is 0 and no modifier key is down; Enter with a modifier key is passed to the anchor, and Space calls the anchor's click(). A disabled link item is the anchor without href, with aria-disabled="true". An href attribute on tanrei-menu-checkbox-item, tanrei-menu-radio-item or tanrei-submenu is ignored and reported (MNU-57). TanreiMenuItem(_ title:, role:, action:), as Button(role:); role: .destructive is destructive. 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 and after isPresented is written, which an app that routes addresses itself answers with its own OpenURLAction; target: TanreiLinkTarget = .same and targetLabel (the accessibility hint), as the Button's. href: String? = null makes it a link: opened through LocalUriHandler.current after onClick and onDismissRequest (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 no role; target: TanreiLinkTarget = TanreiLinkTarget.Same, and targetLabel is appended to the state description, as the Button's. As MenuItemButton: a null onPressed disables it. href: Uri? = null makes it a link (Semantics(link: true) with the address as its linkUrl), opened by the nearest TanreiLinkOpener after onPressed and after the menu has closed; target is a TanreiLinkTarget and targetSemanticLabel the semantics hint, as the Button's. href?: string on an entry whose kind is 'item' makes it a link (accessibilityRole="link"): onPress receives an event whose preventDefault() keeps the menu from calling Linking.openURL(href); target is "same" | "new" and targetLabel the accessibilityHint, as the Button's. An href on an entry of another kind is ignored and reported (MNU-57).

checkbox-item

An option that is switched on or off on its own, such as "Show grid". It is marked as a checkbox unless the design system, its group or the item says otherwise.

プロパティ 型 既定値 説明
label string "" The item's text and its accessible name. Required.
supportingText string "" A line below the label that says more about the item.
leadingIcon string "" The name of an icon (a glyph of the system's icon set) before the label.
trailingIcon string "" The name of an icon at the end of the row.
trailingText string "" Text at the end of the row that is not a keyboard shortcut, such as a count or "Beta". An item's shortcut goes in shortcut; an item that has both draws the shortcut.
shortcut strings The item's keyboard shortcut, in the keyboard shortcut component's key names: one chord per string, pressed in order (["mod+c"]). The menu draws it at the end of the row for the keyboard the person uses and exposes it as the item's keyboard shortcut (MNU-24, MNU-31). It does not make the keys work; the product binds them.
checked boolean false Whether the option is on. It is the item's own: the menu switches it when the item is activated and emits the item's change.
markIcon string "" The name of a glyph of the system's icon set to mark this item with while it is checked, instead of the mark the design system chooses ("check" draws a plain check). Empty takes the group's markIcon, and then the design system's choice (MNU-38).
disabled boolean false Prevents activation. The item stays reachable with the arrow keys.
イベント 説明
change The item was activated; carries its new checked.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tanrei-menu-checkbox-item TanreiMenuToggle TanreiCheckboxMenuItem TanreiCheckboxMenuItem MenuCheckboxItem
label · プロパティ label title text child label
supportingText · プロパティ supportingText supportingText supportingText supportingText supportingText
leadingIcon · プロパティ leadingIcon leadingIcon leadingIcon leadingIcon leadingIcon
trailingIcon · プロパティ trailingIcon trailingIcon trailingIcon trailingIcon trailingIcon
trailingText · プロパティ trailingText trailingText trailingText trailingText trailingText
shortcut · プロパティ shortcut shortcut shortcut shortcut shortcut
checked · プロパティ checked isOn checked value checked
markIcon · プロパティ markIcon markIcon markIcon markIcon markIcon
disabled · プロパティ disabled disabled enabled onChanged disabled
change · イベント change isOn onCheckedChange onChanged onCheckedChange
備考 TanreiMenuToggle(_ title:, isOn:), as Toggle in a menu; writing the binding is change. As CheckboxMenuButton: value is checked, a null onChanged disables it.

radio-item

One choice of several in its group, such as "Sort by name"; its group holds which one is checked. It is marked as a radio button unless the design system, its group or the item says otherwise.

プロパティ 型 既定値 説明
label string "" The item's text and its accessible name. Required.
supportingText string "" A line below the label that says more about the item.
leadingIcon string "" The name of an icon (a glyph of the system's icon set) before the label.
trailingIcon string "" The name of an icon at the end of the row.
trailingText string "" Text at the end of the row that is not a keyboard shortcut, such as a count or "Beta". An item's shortcut goes in shortcut; an item that has both draws the shortcut.
shortcut strings The item's keyboard shortcut, in the keyboard shortcut component's key names: one chord per string, pressed in order (["mod+c"]). The menu draws it at the end of the row for the keyboard the person uses and exposes it as the item's keyboard shortcut (MNU-24, MNU-31). It does not make the keys work; the product binds them.
value string "" Identifies the choice: the item is checked while its group's value equals it. Unique within its group; required.
markIcon string "" The name of a glyph of the system's icon set to mark this item with while it is checked, instead of the mark the design system chooses ("check" draws a plain check). Empty takes the group's markIcon, and then the design system's choice (MNU-38).
disabled boolean false Prevents activation. The item stays reachable with the arrow keys.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tanrei-menu-radio-item TanreiMenuOption TanreiRadioMenuItem TanreiRadioMenuItem MenuRadioItem
label · プロパティ label title text child label
supportingText · プロパティ supportingText supportingText supportingText supportingText supportingText
leadingIcon · プロパティ leadingIcon leadingIcon leadingIcon leadingIcon leadingIcon
trailingIcon · プロパティ trailingIcon trailingIcon trailingIcon trailingIcon trailingIcon
trailingText · プロパティ trailingText trailingText trailingText trailingText trailingText
shortcut · プロパティ shortcut shortcut shortcut shortcut shortcut
value · プロパティ value tag value value value
markIcon · プロパティ markIcon markIcon markIcon markIcon markIcon
disabled · プロパティ disabled disabled enabled enabled disabled
備考 TanreiMenuOption(_ title:).tag(value) inside a section with a selection, as a Picker's tagged options. As RadioMenuButton under a RadioGroup: the item takes its value and enabled, the group the selection.

submenu · グループ — アイテムを含む

An item that opens another level of the menu beside it: it holds that level's items, of any kind, further submenus included. It is not an action and has no shortcut.

プロパティ 型 既定値 説明
label string "" The item's text and its accessible name. Required.
supportingText string "" A line below the label that says more about the item.
leadingIcon string "" The name of an icon (a glyph of the system's icon set) before the label.
trailingText string "" Text at the end of the row, before the submenu indicator: the current value of what the submenu sets ("English"), or a count.
open boolean false Whether the item's submenu is shown. It is the item's own: the menu sets it when the submenu is opened or closed and emits the item's toggle; the product may also set it. It shows nothing while the surface that holds the item is not shown.
disabled boolean false Prevents the submenu from opening. The item stays reachable with the arrow keys.
イベント 説明
toggle The item's submenu was opened or closed by the person; carries its new open.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tanrei-submenu TanreiSubmenu TanreiSubmenu TanreiSubmenu MenuSubmenu
label · プロパティ label title text child label
supportingText · プロパティ supportingText supportingText supportingText supportingText supportingText
leadingIcon · プロパティ leadingIcon leadingIcon leadingIcon leadingIcon leadingIcon
trailingText · プロパティ trailingText trailingText trailingText trailingText trailingText
open · プロパティ open isExpanded expanded controller open
disabled · プロパティ disabled disabled enabled enabled disabled
toggle · イベント toggle isExpanded onExpandedChange onOpenChanged onOpenChange
備考 <tanrei-submenu label="Open recent"> holds the submenu's items as its children, as tanrei-menu holds its own; it draws the row in its parent's surface and its own surface as a manual popover in the top layer. open reflects, and toggle fires on it and bubbles. TanreiSubmenu(_ title:, isExpanded:) { … }, as a Menu nested in a Menu; the binding is optional, and writing it is toggle. disabled follows .disabled(_:). TanreiSubmenu(text, expanded, onExpandedChange) { … }: the caller keeps expanded (state hoisting), and an overload without the pair keeps it itself. enabled is the inverse flag. As SubmenuButton: TanreiSubmenu(child:, menuChildren: […], controller:, onOpenChanged:); the optional MenuController opens and closes it, as the anchor's does the menu. { kind: 'submenu', label, items: […], open?, onOpenChange? }: uncontrolled unless open is given.

group · グループ — アイテムを含む

A run of related items, optionally titled. The menu separates a group from what is next to it, by a divider or a gap, and a group of radio items is one set of choices.

プロパティ 型 既定値 説明
title string "" A short heading drawn above the group's items, which also names the group; empty draws none and leaves the group unnamed.
value string null The value of the group's checked radio item; null checks none. The group sets it when a radio item is chosen and emits its change; the product may also set it. Unused by a group without radio items.
markIcon string "" The name of a glyph of the system's icon set that marks the group's checked checkbox and radio items, for those that name none of their own ("check" draws a plain check). Empty takes the design system's choice (MNU-38).
イベント 説明
change A radio item of the group was chosen; carries the group's new value.
仕様 Web SwiftUI Jetpack Compose Flutter React Native
アイテム tanrei-menu-group TanreiMenuSection TanreiMenuGroup TanreiMenuGroup MenuGroup
title · プロパティ label title title title title
value · プロパティ value selection selectedValue groupValue value
markIcon · プロパティ markIcon markIcon markIcon markIcon markIcon
change · イベント change selection onSelectedValueChange onChanged onValueChange
備考 The title is the label attribute, as <optgroup label>: a title attribute would also draw the browser's tooltip. The group sets its own value before firing change. TanreiMenuSection(_ title:) { … } as Section; TanreiMenuSection(_ title:, selection:) { TanreiMenuOption … } for radio items, as an inline Picker. Writing the binding is change. TanreiMenuGroup(title, selectedValue, onSelectedValueChange) { TanreiRadioMenuItem(text, value) }; the caller keeps selectedValue. TanreiMenuGroup<T>(title:, groupValue:, onChanged:, children:), which is also the RadioGroup of its radio items. { kind: 'group', title?, value?, onValueChange?, items: (MenuItem | MenuCheckboxItem | MenuRadioItem)[] }.

状態

振る舞い

EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。

ID 要件
MNU-1 WHEN open becomes true THE menu SHALL present its surface in the platform's overlay layer (the web's top layer through the popover API, SwiftUI's popover, Compose's Popup, Flutter's Overlay, React Native's overlay layer that TanreiProvider renders), above other content at comp.menu.surface.elevation, and SHALL move focus to its first enabled item, wherever that item stands in the surface: inside a group as well, a menu whose every item is in a group included, and a menu the product mounts with open true included; WHEN open becomes false THE menu SHALL remove the surface and, if focus was inside it, return focus to the anchor (a context menu: to the element MNU-62 names). A menu is an operated overlay, so it takes focus and returns it although it is not modal.
MNU-2 WHILE trigger is anchor THE menu SHALL place its surface comp.menu.surface.offset from the anchor on the side and edge that placement names (bottom-start: below the anchor with the start edges aligned; bottom-end: below with the end edges aligned; top-start and top-end: above); WHERE that side has less room than the surface needs within comp.menu.surface.viewport-margin of the edge of the visible part of the window, above the on-screen keyboard while one is shown, and the other side has more THE menu SHALL flip to the other side; it SHALL shift along the anchor to stay within that margin, SHALL limit its height to the least of the height its items need, the most height the product's own style gives the surface where it gives one (on the web, max-block-size on the surface part), and the room left, place the surface by that height and scroll its items beyond it, and SHALL keep following the anchor while it moves. WHEN the on-screen keyboard shows or hides, or the visible part of the window otherwise moves or changes size, THE menu SHALL place its surface again inside what is visible. WHEN the layout direction is right-to-left start and end SHALL mirror.
MNU-3 THE menu SHALL make its surface at least comp.menu.surface.min-width and at most comp.menu.surface.max-width wide — as wide as its widest item within those bounds — inset its items by comp.menu.surface.padding-block and comp.menu.surface.padding-inline, fill it with comp.menu.surface.color, round it by comp.menu.surface.shape eased by sys.shape.corner-smoothing, and clip its items to that shape.
MNU-4 WHEN the menu opens THE menu SHALL fade its surface in and grow it from the edge that faces the anchor over comp.menu.motion.open-duration with comp.menu.motion.open-easing, and WHEN it closes fade it out over comp.menu.motion.close-duration with comp.menu.motion.close-easing; WHERE motion is reduced it SHALL appear and disappear at once. WHEN it is mounted with open true THE menu SHALL draw its surface, and the surface of every submenu open in it (MNU-50), without that motion while the document has not yet been first painted, and with it when it is mounted after that.
MNU-5 WHEN the user presses Escape or the platform's back while no submenu is open, presses outside every surface of the menu and the anchor (the area of a context menu is outside, MNU-62), presses Tab or Shift+Tab at any level, or moves focus out of the menu and all of its submenus THE menu SHALL emit close once with that reason and SHALL be closed, every open submenu with it (MNU-49): through its binding or controller where open is one (SwiftUI, Flutter), and by the product setting open to false where open is a plain value (web, Compose, React Native); Tab and Shift+Tab SHALL then move focus on from the anchor as they would have from it (a context menu: from the element MNU-62 names). Whether a press outside also reaches the element under it SHALL follow the platform and be stated in its README.
MNU-6 WHEN the user activates an enabled item (an action) at any level by pointer, touch, Enter or Space THE menu SHALL emit the item's click once, then emit close and be closed as MNU-5 says, every open submenu with it, an item that is a link (MNU-53) opening its destination after that, and only for an activation that MNU-54 counts; WHEN the user activates an enabled checkbox item THE menu SHALL switch its checked and emit the item's change once with the new checked; WHEN the user activates an enabled radio item whose value is not its group's value THE menu SHALL set the group's value to the radio item's value, so that the group's other radio items become unchecked, and emit the group's change once; activating a radio item that is already checked SHALL emit no change. After a checkbox or radio item, a checked radio item included, the menu SHALL close as for an action when the activation came from pointer, touch or Enter, and SHALL stay open, at every level, when it came from Space. Activating a submenu item opens its submenu and closes nothing (MNU-43).
MNU-7 WHILE the menu is open THE menu SHALL move focus among the items of the surface that holds focus — the menu's own or one submenu's, never across levels — to the next and previous item with Down Arrow and Up Arrow, wrapping at the ends and passing through groups as if their items were the surface's, to the first and last item with Home and End, and to the next item of that surface whose label begins with the typed characters when printable characters are typed within one second of each other; disabled items SHALL be reachable but SHALL NOT activate, a submenu item SHALL take focus as any other item, and dividers, groups and group titles SHALL NOT take focus.
MNU-8 WHEN a pointer moves onto an item THE menu SHALL move focus to that item without showing the focus ring and render it hovered, so that the arrow keys continue from it, except while the pointer is on its way to an open submenu (MNU-45); WHEN the pointer leaves the surface focus SHALL stay on that item.
MNU-9 WHILE an item's disabled is true THE menu SHALL NOT emit its click or change, nor its group's change, for it and SHALL expose the disabled state to assistive technology.
MNU-10 WHILE an item is hovered, keyboard-focused or pressed THE menu SHALL render its container, label, supporting text, trailing text, icons, mark (a glyph's mark color, and a control look's control and control-mark colors) 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.
MNU-11 WHEN an item receives keyboard focus THE menu SHALL show the focus ring inside the item's container, which the surface would otherwise clip, without animation; focus gained by pointer or touch SHALL NOT show it.
MNU-12 WHERE an item is shorter than the minimum target THE menu SHALL make its container at least sys.target.touch tall for touch input, in both densities, because stacked items cannot extend a hit area over each other, and for fine pointers at least sys.target.pointer tall WHILE density is regular and as tall as its density gives WHILE density is compact (MNU-64), the one place where a row of the menu is lower than the pointer target; a platform that cannot tell the input type SHALL use sys.target.touch. The whole row SHALL be the item's hit area.
MNU-13 WHILE an item's disabled is true THE menu SHALL render its container, label, supporting text, trailing text, icons, mark (a glyph's mark color, and a control look's control and control-mark colors) and opacity from the disabled component tokens of its condition.
MNU-14 WHEN placement names a value the menu does not define THE menu SHALL render with the default value, and in debug builds report the unknown value.
MNU-15 WHEN the menu item is pressed by pointer, touch, Enter or Space THE menu item 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.
MNU-16 WHILE pressed THE menu item 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, supporting text, trailing text, icons, mark, 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.menu.motion.feedback-duration with comp.menu.motion.state-easing, fades over comp.menu.motion.state-duration with comp.menu.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
MNU-17 THE menu item 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.
MNU-18 THE menu SHALL draw an item from the condition it is in: destructive for an item (an action) whose destructive is true, selected for a checkbox item whose checked is true and for a radio item whose value equals its group's value, current for a link item whose current is true (MNU-58), expanded for a submenu item whose submenu is open, otherwise unselected.
MNU-19 THE menu SHALL lay out an item as one row: comp.menu.container.padding-inline.regular from each edge, the mark column and the leading icon column, each where MNU-59 keeps it, the label with the supporting text below it taking the remaining width, then the trailing column, which holds the shortcut or the trailing text, and the trailing icon column, which holds the trailing icon or, in a submenu item, the submenu indicator (MNU-42) or, in a link item that opens a new context and names no trailing icon, its mark (MNU-55), each comp.menu.container.gap.regular from the next; an empty trailing icon column SHALL keep its width in every item of the same surface so that shortcuts, trailing text and trailing icons align, each surface — the menu's and each submenu's — reserving its own, and WHEN the layout direction is right-to-left THE menu SHALL mirror the row.
MNU-20 THE menu SHALL make an item at least comp.menu.container.height.regular tall, pad its content by comp.menu.container.padding-block.regular above and below, draw the label in comp.menu.label.typescale, the supporting text in comp.menu.supporting-text.typescale and the trailing text and the shortcut's legends in comp.menu.trailing-text.typescale, and grow with its content and with enlarged text rather than clip or truncate it.
MNU-21 THE menu SHALL draw leadingIcon at comp.menu.leading-icon.size.regular and trailingIcon at comp.menu.trailing-icon.size.regular through the icon helper, with the family and fill of the item's condition (selected for a checked item and for a current link item, otherwise unselected), sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max; an empty name SHALL draw nothing.
MNU-22 WHILE a checkbox or radio item is checked and its mark is a glyph (MNU-38) THE menu SHALL draw that glyph of the system's icon set — check where the design system's choice is check, the named glyph where markIcon names one — in its mark column, centered in a box of side comp.menu.mark.size.regular, in the mark color of its condition, with sys.icon.family.selected, sys.icon.fill.selected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, and SHALL draw nothing in that column while the item is not checked; it SHALL NOT mirror the check when the layout direction is right-to-left, a glyph that markIcon names following MNU-33; THE menu SHALL draw no mark of its own (no path, shape or character of its own for the check). WHEN checked changes while the menu is open THE menu SHALL fade the glyph in or out over comp.menu.motion.state-duration with comp.menu.motion.state-easing without moving or scaling it, and at once where motion is reduced.
MNU-23 WHILE the menu's separation is divider (MNU-35) THE menu SHALL draw a line of comp.menu.divider.thickness in comp.menu.divider.color across the surface's inner width, inside comp.menu.surface.padding-inline on each side (the same inset as the rows, not the rows' own padding) between a group and an adjacent group or an adjacent item outside any group, with comp.menu.divider.space above and below it, and SHALL expose it as a separator that takes no focus; it SHALL draw no line before the first or after the last element of the surface.
MNU-24 THE menu SHALL expose the menu role, named by its anchor (a context menu as MNU-63 says); each item as a menu item, a link item included (MNU-53), each checkbox item as a menu item checkbox and each radio item as a menu item radio with its checked state, whatever its mark looks like, each with its position and the size of the surface it is in, named by label and described by supporting text, a link item that opens a new context also by its targetLabel (MNU-55), and a link item that is current with the current state (MNU-58); each submenu item as a menu item that opens a menu, with whether that menu is open (aria-haspopup with the value menu, and aria-expanded, on the web), described also by its trailing text, and each submenu's surface as a menu of its own named by its submenu item's label; shortcut SHALL be exposed as the item's keyboard shortcut in the form KBS-12 gives (aria-keyshortcuts on the web) where the platform has that property, and otherwise the text KBS-10 gives as the item's description; trailing text SHALL be exposed as the item's description only, never as a keyboard shortcut, whatever it holds; icons, the mark and the submenu indicator SHALL be hidden; each group SHALL be exposed as a group, named by its title where it has one. WHILE trigger is anchor THE menu SHALL also expose on its anchor that it opens a menu and whether the menu is open, where the platform has that property.
MNU-25 WHILE the menu is open THE menu SHALL NOT trap focus, SHALL NOT make the rest of the screen inert and SHALL draw no scrim: it is an operated overlay that is not modal.
MNU-26 WHILE an item's label is empty THE menu SHALL still render the item, and in debug builds report that it has no accessible name.
MNU-27 WHEN the colors of an item change between states THE menu SHALL animate them over comp.menu.motion.state-duration with comp.menu.motion.state-easing, and SHALL change them at once where motion is reduced.
MNU-28 WHILE a group's title is not empty THE menu SHALL draw it above the group's items in comp.menu.group-title.typescale and comp.menu.group-title.color, starting comp.menu.group-title.padding-inline.regular from the rows' start edge and ending comp.menu.group-title.padding-inline.regular from their end edge, whether or not the menu draws the mark and leading icon columns (so that, at the default values, the title starts where each row's first column starts: the mark, the leading icon or the label), with comp.menu.container.padding-block.regular above and below, wrapping and growing with enlarged text rather than clipping; WHEN the layout direction is right-to-left start and end SHALL mirror. The title SHALL take no focus and SHALL NOT be read as an item.
MNU-29 WHEN a radio item is not inside a group, or a group's value names none of its radio items THE menu SHALL draw every such radio item unchecked; WHEN two radio items of one group share a value THE menu SHALL check both while the group's value names it; WHEN a group holds another group THE menu SHALL draw the inner group's items as the outer group's, with no title or divider of the inner group's own; in debug builds it SHALL report each of these.
MNU-30 IF the icon set cannot draw a glyph the menu draws yet — the check or the named glyph of a mark, the check inside a checked box, the submenu indicator — because its font is still loading or failed to load THE menu SHALL draw nothing in the glyph's place, never the glyph's name as text, SHALL keep its column's width, SHALL still draw the box of a checkbox look and the ring of a radio look, SHALL still expose the item's checked state and whether its submenu is open, and SHALL show the glyph once the icon set can draw it, without the fade of MNU-22.
MNU-31 WHILE an item's shortcut is not empty THE menu SHALL draw it in the item's trailing column with the keyboard shortcut component, hosted as KBS-13 says: at size small, in the form comp.menu.shortcut.variant names, with convention auto, and disabled while the item is disabled; its legends and separators SHALL take comp.menu.trailing-text.typescale and the trailing-text color of the item's condition and state, and its caps the keyboard shortcut's own tokens. IF an item has both a shortcut and trailing text THE menu SHALL draw the shortcut, not the trailing text, and in debug builds report the item.
MNU-32 THE menu 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 menu is shown THE menu SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus, the surface placed again by the new direction (MNU-2).
MNU-33 WHERE the layout direction is right-to-left THE menu SHALL draw an item's leading icon and trailing icon, the glyph a markIcon names and the submenu indicator mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size, color and fill nor the order MNU-19 gives. THE menu SHALL NOT mirror a glyph itself, and SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name. The check — the check of a mark and the check inside a checked box — is never mirrored (MNU-22, MNU-39).
MNU-34 THE menu SHALL lay out an item's label, supporting text and trailing text and a group's title as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE menu SHALL still place each and align its lines by the layout direction, not by the text's own.
MNU-35 THE menu SHALL separate a group from an adjacent group or an adjacent item outside any group as its separation names: WHILE separation is auto, as comp.menu.separation names, which follows sys.behavior.separation unless a design system sets it apart; WHILE separation is divider or gap, as that value names, whatever the tokens say; divider draws the line of MNU-23 and gap leaves the space of MNU-36. Every submenu SHALL separate its groups as its menu does. WHEN separation, or the token it follows, changes THE menu SHALL draw the other form at once, without animation, keeping its open state and its focus; WHEN separation names a value the menu does not define THE menu SHALL treat it as auto and in debug builds report the unknown value. A platform that cannot draw a choice SHALL fall back to the first value, divider, and state that in its README.
MNU-36 WHILE the menu's separation is gap (MNU-35) THE menu SHALL draw each group, and each run of adjacent items outside any group, as a tile of its own: a sheet of comp.menu.surface.color with the corners comp.menu.surface.shape eased by sys.shape.corner-smoothing and the elevation comp.menu.surface.elevation, its ring drawn inside the tile's edge, holding its rows inside comp.menu.surface.padding-block and comp.menu.surface.padding-inline as the surface holds them; it SHALL leave comp.menu.group.gap of empty space between two tiles, through which what is under the menu shows, and SHALL draw no sheet, edge or elevation of its own around the tiles. It SHALL draw no line between tiles and SHALL expose no separator, the groups staying exposed as groups (MNU-24) and a tile being nothing to assistive technology; it SHALL leave no space before the first or after the last tile, and a group's title SHALL keep its own padding inside its tile (MNU-28). The tiles of a menu taller than the room it has SHALL scroll together inside the menu's bounds (MNU-2), and every submenu SHALL draw its tiles the same way: the gap tells the parts of a menu apart by cutting its sheet, as the gap of a button group or of an accordion makes separate shapes, never by empty space on one sheet.
MNU-37 WHILE a checkbox or radio item is checked THE menu SHALL fill its row, within the corners comp.menu.container.shape gives, with the container color of the selected condition in its state (comp.menu.selected.container.color at rest), SHALL draw that fill over its surface (comp.menu.surface.color) and under the row's content, so that a container color that is not opaque lets the surface show through it and nothing else, and SHALL draw its label, supporting text, trailing text and icons in that condition's colors; checked rows next to each other SHALL each keep their own corners and SHALL NOT be joined into one shape. THE menu SHALL NOT show a choice by the fill alone: a checked item always draws its mark (MNU-38), and a design system whose selected container color is none still marks it. The mark is what tells a checked item; the fill is a second cue a design system may give or withhold, through comp.menu.selected.container.color for each contrast scheme, and the menu SHALL read that token in the scheme it is drawn in and SHALL NOT decide the fill by the scheme itself. The default system gives it at medium and high contrast, where a person has asked for stronger cues, and withholds it at standard contrast, so that there the one filled row of a menu is the row the person is on (MNU-58).
MNU-38 THE menu SHALL draw the mark of a checkbox or radio item as the first of these that applies: the glyph the item's markIcon names; the glyph its group's markIcon names; otherwise what the design system chooses for the item's kind — comp.menu.checkbox-item.mark for a checkbox item (checkbox: the checkbox look of MNU-39; check: the icon set's check, as MNU-22 draws it) and comp.menu.radio-item.mark for a radio item (radio: the radio look of MNU-40; check: the same check). Whatever the mark is THE menu SHALL place it in the mark column, the first column of the row (MNU-19), before the leading icon, centered in a box of side comp.menu.mark.size.regular, SHALL hide it from assistive technology, SHALL NOT make it a control of its own (it takes no focus and no press and has no role apart from its item's), and SHALL keep exposing the item as a menu item checkbox or a menu item radio with its checked state (MNU-24). A platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
MNU-39 WHILE a checkbox item's mark is the checkbox look (MNU-38) THE menu SHALL draw, whether the item is checked or not, a box of side comp.menu.control.box.size with corners comp.menu.control.box.shape eased by sys.shape.corner-smoothing: WHILE the item is not checked the box SHALL be empty, with an outline of comp.menu.control.outline.width inside its edge in the control color of its condition and state (comp.menu.unselected.control.color at rest); WHILE it is checked the box SHALL be filled in the control color (comp.menu.selected.control.color at rest), outline and fill being one shape with no seam between them, with the glyph check of the system's icon set centered on it at comp.menu.control.mark.size in the control-mark color of its condition and state, drawn with sys.icon.family.selected, sys.icon.fill.selected, the weight comp.menu.control.mark.weight (not sys.icon.weight: the check in the box is a control's own mark, as the checkbox's is, CHK-12) and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, not clipped by the box and never mirrored; the weight SHALL change the stroke of the glyph only. THE menu SHALL draw no round state layer behind the box: the row is the item's container.
MNU-40 WHILE a radio item's mark is the radio look (MNU-38) THE menu SHALL draw, whether the item is checked or not, a ring of outer diameter comp.menu.control.ring.size in the control color of its condition and state, the outer diameter the same in both conditions: WHILE the item is not checked the ring SHALL be stroked at comp.menu.control.ring.width inside that diameter; WHILE it is checked it SHALL be stroked at comp.menu.control.ring.selected-width-share of the ring's radius, or at comp.menu.control.ring.width where that is wider, the stroke growing inward, and the area inside the ring's inner edge, less comp.menu.control.fill.inset all round, SHALL be filled in the same control color at comp.menu.control.fill.opacity, ring and fill being one shape where they touch; a fill whose diameter would be zero or less SHALL NOT be drawn. THE menu SHALL draw no dot and no round state layer of its own.
MNU-41 WHEN checked changes while the menu is open and the item's mark is a control look THE menu SHALL move the box's outline and fill, the check inside it, and the ring's stroke and fill from the old condition to the new over comp.menu.motion.state-duration with comp.menu.motion.state-easing — the check fading, the ring's stroke growing or thinning inward — without moving or resizing the box or the ring's outer diameter, and the row's fill SHALL change with it (MNU-27); WHERE motion is reduced it SHALL draw the new condition whole at once.
MNU-42 THE menu SHALL draw a submenu item as an item's row (MNU-19, MNU-20) with the submenu indicator in its trailing icon column: the glyph chevron_right of the system's icon set at comp.menu.trailing-icon.size.regular in the trailing-icon color of the item's condition and state, 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; its trailing text, where it has one, SHALL be drawn in the trailing column before the indicator, and a submenu item SHALL have no shortcut, no trailing icon of the product's and no mark. THE menu SHALL draw no chevron of its own (no path, shape or character of its own).
MNU-43 WHEN the user presses Enter or Space on an enabled submenu item, presses the arrow key that points to the inline end on it (Right Arrow in a left-to-right layout, Left Arrow in a right-to-left one), presses it with a pointer or taps it THE menu SHALL open that item's submenu without delay, close any other open submenu of the same surface, move focus to the submenu's first enabled item (its first item where none is enabled), showing the focus ring only when the input was a key, set the item's open to true and emit the item's toggle once with true; it SHALL NOT emit close and SHALL close nothing else. WHEN the submenu is already open the same input SHALL move focus to its first enabled item and emit nothing. WHEN the product sets a submenu item's open to true while the surface that holds the item is shown THE menu SHALL open the submenu without moving focus and without emitting toggle.
MNU-44 WHEN a pointer that can hover rests on an enabled submenu item for comp.menu.motion.submenu-delay THE menu SHALL open that item's submenu, closing any other open submenu of the same surface, and SHALL leave focus on the submenu item (MNU-8) until the pointer enters the submenu or a key moves it; a pointer that leaves the item sooner SHALL open nothing. Touch has no hover: a tap opens the submenu (MNU-43). WHERE motion is reduced the delay SHALL still apply: it reads the pointer's intent and is not an animation.
MNU-45 WHILE a submenu is open and the pointer moves from its submenu item towards it THE menu SHALL keep the submenu open and SHALL NOT move hover or focus to the items the pointer crosses on the way (MNU-8): the pointer is on its way while it keeps moving inside the triangle whose corners are the pointer's last position on the submenu item and the two corners of the submenu's surface on the side that faces the item. WHEN the pointer leaves that triangle without entering the submenu, or rests inside it for comp.menu.motion.submenu-delay THE menu SHALL treat the item under the pointer as MNU-8 says, which closes the submenu when that item is another one (MNU-48). The triangle SHALL be taken anew each time the pointer leaves the submenu item and SHALL follow the side the submenu is placed on (MNU-46), so that it mirrors in a right-to-left layout and when the submenu is placed on the other side.
MNU-46 THE menu SHALL place a submenu's surface beside the surface that holds its submenu item, comp.menu.submenu.offset from that surface's inline-end edge (to the right in a left-to-right layout, to the left in a right-to-left one), with the block-start edge of its first row level with the block-start edge of the submenu item's row, and SHALL draw it above the surface it opened from; WHERE that side has less room than the surface needs within comp.menu.surface.viewport-margin of the edge of the visible part of the window, above the on-screen keyboard while one is shown, and the inline-start side has more THE menu SHALL place it on the inline-start side instead, the same distance from that edge, and a submenu opened from a surface placed that way SHALL try that side first; it SHALL shift the surface along the block axis to stay within that margin, SHALL limit its height to the least of the height its items need, the most the product's own style gives the submenu's surface (MNU-2) and the room left and scroll its items beyond it, and SHALL keep it in place against its submenu item while that item moves. WHEN the submenu item is scrolled out of the visible part of its surface THE menu SHALL close the submenu (MNU-48).
MNU-47 WHERE neither side of the surface that holds a submenu item has room for the submenu's surface at comp.menu.surface.min-width within comp.menu.surface.viewport-margin THE menu SHALL still present the submenu as a surface of its own, laid over the surface it opened from, on the side with more room and as far towards that side as the margin allows, narrowed down to comp.menu.surface.min-width before it covers more, and SHALL leave at least sys.target.touch of the covered surface's inline size uncovered on its other side wherever the visible part of the window is wide enough for both; THE menu SHALL NOT replace the items of a surface with its submenu's at any window size. WHILE a submenu covers part of the surface it opened from, a press on the uncovered part of that surface SHALL close the submenu as MNU-48 says and SHALL NOT activate the item under the press, so that a person without a keyboard or a back button can go back one level; keys and focus SHALL behave as in a wide window.
MNU-48 WHEN focus is in a submenu and the user presses the arrow key that points to the inline start (Left Arrow in a left-to-right layout, Right Arrow in a right-to-left one), Escape or the platform's back THE menu SHALL close that submenu and every submenu opened from it, and nothing else, return focus to its submenu item, showing the focus ring only when the input was a key, set the item's open to false and emit the item's toggle once with false, without emitting close. WHEN focus or hover moves to another item of the surface that holds the submenu item (MNU-7, MNU-8), WHEN the submenu item becomes disabled or is removed, or WHEN the product sets its open to false THE menu SHALL close the submenu the same way — the product's own change emitting no toggle — and focus that was inside it SHALL move to the submenu item, or to the item that took focus. Escape and the platform's back SHALL close one level for each press, and the menu itself only when no submenu is open (MNU-5). THE menu SHALL keep at most one submenu open in each surface.
MNU-49 WHEN the menu is closed — for a reason MNU-5 or MNU-6 names, at whatever level it arose, or because the product sets open to false — THE menu SHALL close every open submenu with it at once, emit close once and never once for each level, set each closed submenu item's open to false without emitting its toggle, and return focus to the anchor as MNU-1 says (a context menu: to the element MNU-62 names), not to a submenu item; a press inside any surface of the menu SHALL NOT count as a press outside, and focus moving between the menu's surfaces SHALL NOT count as focus leaving it. The surfaces SHALL fade out together as MNU-4 says.
MNU-50 WHILE a submenu is open THE menu SHALL draw its submenu item in the expanded condition (MNU-18) in whatever interaction state it is in, so that the path from the menu to the surface in use stays marked while the pointer and focus are elsewhere, drawing that condition's container color over its surface as MNU-37 draws a checked row's, and SHALL draw the submenu's surface as the menu's own (MNU-3) from the same tokens, its items following every behaviour of the menu's items, with the open and close motion of MNU-4 growing from the edge that faces its submenu item.
MNU-51 WHEN the user presses the arrow key that points to the inline end on an item that is not a submenu item, or the arrow key that points to the inline start in the menu's own surface THE menu SHALL do nothing and SHALL NOT let the key scroll or act on what is behind the menu. WHILE a submenu item's disabled is true THE menu SHALL NOT open its submenu by any input, SHALL close it if it is open (MNU-48), and SHALL still expose that the item opens a menu, with the disabled state (MNU-9).
MNU-52 THE menu SHALL let a submenu hold items of every kind, groups and further submenus included, to any depth, each level behaving as MNU-42 to MNU-51 say, and SHALL keep the radio items of a submenu apart from those of every other surface: a group's value reaches only the radio items that are its own. WHEN a submenu item lies more than two levels of submenu below the menu, or WHEN a submenu holds no item THE menu SHALL still render what it can — a submenu with no item draws its row and its indicator, takes focus and opens nothing — and in debug builds report each. WHILE open is true on a submenu item whose own surface is not shown THE menu SHALL show nothing for it until that surface is shown.
MNU-53 WHILE an item's href is not empty THE menu SHALL make the item a link item: 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 — on the web its context menu, opening in a new tab or window, copying and dragging its address, and the address shown before it is followed — is the platform's, and not the menu's to draw or to take over. WHERE the platform has a menu item role (the web) THE menu SHALL expose a link item with that role, on the link itself, and not with the link role: a menu's own role, an item's position in it and the keys of MNU-7 hold only while everything the menu holds is a menu item, and a menu of links is operated as a menu; WHERE the platform's menu items are buttons and it has no such role THE link item SHALL carry the platform's link role or trait in place of the button's. Everything this spec says of an item (an action) other than what MNU-54 to MNU-57 say — its row and columns, its conditions, destructive included, its states, focus ring, minimum height, press answer, corners and motion, its place in the order of the arrow keys, Home, End and typed characters, and focus following the pointer (MNU-7, MNU-8, MNU-10 to MNU-21, MNU-27, MNU-31) — SHALL hold for a link item from the same tokens, with no look of its own for a link and none for a destination already visited; a link item SHALL NOT be a Tab stop, as no item of a menu is.
MNU-54 WHEN the user activates an enabled link item at any level as an item of a menu 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 menu SHALL emit the item's click exactly once, then emit close with the reason item and be closed as MNU-6 says for an action, and then open href where target names, unless the product cancelled that click, in which case it SHALL still close and SHALL open nothing: the product's own router has taken the navigation. It SHALL show the pressed state as MNU-15 and MNU-16 say and SHALL NOT hold the closing or the navigation back for sys.motion.duration.press-hold. Space SHALL activate a link item exactly as Enter does and SHALL scroll nothing: a link item is exposed and operated as a menu item (MNU-53), nothing in its look tells it from the action in the next row, Space activates every other row of the menu, and behind an open menu Space has no page to scroll (MNU-51). 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 menu SHALL leave the link to the platform, SHALL prevent, replace or imitate none of it, SHALL NOT emit close and SHALL stay open at every level with focus where it was, so that a person can open several of a menu's links in other tabs one after another; what MNU-5 says of a press outside and of focus leaving the menu still holds.
MNU-55 WHILE an item is a link item and its target is new THE menu SHALL open href in a new context — on the web a new tab or window that is given no handle to the page that opened it; on the other platforms outside the screen the person is on, as the platform's opener decides — and, WHILE the item's trailingIcon is empty, SHALL draw the glyph open_in_new of the system's icon set in the item's trailing icon column, as MNU-21 draws trailingIcon: at comp.menu.trailing-icon.size.regular, in the trailing-icon color of the item's condition and state (comp.menu.unselected.trailing-icon.color at rest, comp.menu.destructive.trailing-icon.color for a destructive item), through the icon helper, which mirrors it in a right-to-left layout because the icon set marks its name (MNU-33), hidden from assistive technology, and held to MNU-30 while the icon set cannot draw it. The mark SHALL reserve the trailing icon column in its surface as a trailing icon does (MNU-19), and the item's shortcut or trailing text SHALL keep its own column before it: the mark stands where a glyph of the product's would, not where the keys are written. WHILE trailingIcon names a glyph THE menu SHALL draw that glyph and no mark of its own: the product has chosen what that place shows. Whether a mark is drawn or not THE menu SHALL expose targetLabel as the end of the item's description, after its supporting text and whatever else MNU-24 puts there, and WHILE targetLabel is empty 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 menu SHALL draw no mark and SHALL ignore targetLabel.
MNU-56 WHILE a link item's disabled is true THE menu SHALL NOT make it a link that can be followed: it SHALL render it as MNU-13 says, the mark of MNU-55 with it, SHALL expose it as MNU-53 exposes a link item, with the disabled state and no destination, SHALL keep it reachable by the arrow keys as MNU-7 keeps any disabled item, SHALL NOT emit its click or close for it, 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).
MNU-57 THE menu SHALL give a destination to the item kind alone. A checkbox item and a radio item hold a state and a submenu item opens another level, so none of them has href, target or targetLabel: a control that holds a state, or a second action, is not a destination; WHERE a platform lets one be written on them (an attribute of the web's element, a field of React Native's item object) THE menu SHALL ignore it, keep the item what it is, and in debug builds report it. WHEN a link item's target names a value the menu does not define THE menu SHALL treat it as same, and in debug builds report the unknown value. IF an 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 menu SHALL make the 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 an item's href is empty THE menu SHALL ignore its target and targetLabel.
MNU-58 WHILE a link item's current is true THE menu SHALL expose it as the current item of its set (on the web aria-current with the value page, on the item's link; on a platform without that state, the selected state) and SHALL draw its row from the current condition: a fill as MNU-37 lays one, in comp.menu.current.container.color at rest, and that condition's colors for its label, supporting text, trailing text and icons, its icons in the selected family and fill, in every state and in every contrast scheme; it SHALL draw no mark in the mark column, keep its label's own weight, and SHALL keep it a link item in every other respect (MNU-53 to MNU-56). The current destination is where the person is, not a choice the menu holds: it carries no check, and it is told from its neighbours by the fill the menu draws for the row the person is on, as a navigation drawer marks its current destination, while assistive technology is told by the state. Its fill is its own and not a checked row's: a checked row has its mark and may go without a fill (MNU-37), the current row has only its fill, so a design system that withholds the one keeps the other. WHERE the platform draws with a person's own forced colors, which remove the fill, THE menu SHALL draw a line of sys.border.thick inside the current item's row in the platform's link color. THE menu SHALL NOT set current itself on any activation: where the product is, is the product's to say. WHILE an item's href is empty THE menu SHALL ignore its current and in debug builds report it.
MNU-59 THE menu SHALL decide the mark column and the leading icon column for each section of a surface — a group, with the items of a group inside it (MNU-29), or a run of adjacent items outside any group: the parts MNU-36 draws as tiles — and each of the two columns by itself. WHILE iconSpace is auto a row SHALL keep a column, empty where it draws nothing in it, WHERE an item of its section draws in it — a checkbox or radio item its mark, whether checked or not and whatever the mark is (MNU-38), an item its leading icon — so that the labels of a section start on one line, and a section where no item draws in a column SHALL keep no room for it, its labels starting that much nearer the row's start; WHILE iconSpace is always every row SHALL keep both columns, whether or not any item of the surface draws in them; WHILE iconSpace is never a row SHALL have a column only where it draws in it itself. The room kept is the column's box (comp.menu.mark.size.regular for the mark, comp.menu.leading-icon.size.regular for the icon) and comp.menu.container.gap.regular after it, draws nothing and is nothing to assistive technology. A submenu item has no mark and so draws nothing in the mark column (MNU-42). Each surface SHALL be decided by itself, every submenu with the menu's iconSpace; a group's title SHALL keep its own place whatever the columns of its section are (MNU-28). WHEN an item is added or removed, or an item's kind or leadingIcon changes, THE menu SHALL decide the item's section again; WHEN iconSpace names a value the menu does not define THE menu SHALL treat it as auto and in debug builds report the unknown value.
MNU-60 WHILE trigger is context, WHEN the person asks for a context menu in the anchor's area — a secondary click (a right click, a Control-click, a two-finger click on a trackpad), the context-menu command of assistive technology, a press of a touch or a pen held within the platform's touch slop (8 points where the platform names none) for the platform's long-press duration (500 ms where the platform names none), or Shift+F10 or the ContextMenu key with no other modifier while focus is on the area or on an element inside it — THE menu SHALL emit contextRequest once, with the point in the viewport's coordinates (where the pointer is, where the press began, or the block-end, inline-start corner of the element that has focus), the element under the pointer or with focus, and how it was asked (pointer, long-press or keyboard), and SHALL NOT open itself: the product sets open to true (MNU-1). Unless the product cancels contextRequest, THE menu SHALL keep the platform's own context menu from answering the request, and after a long press SHALL keep the release of that press from activating what is under it; a cancelled request SHALL be left to the platform. A request that an element inside the area has already taken (on the web, a contextmenu event whose default is prevented) and one made inside the menu's own surfaces SHALL NOT be answered. WHILE trigger is anchor THE menu SHALL answer none of these. WHEN trigger names a value the menu does not define THE menu SHALL treat it as anchor and in debug builds report the unknown value.
MNU-61 WHILE trigger is context THE menu SHALL place its surface at the point of the last contextRequest, with no offset, as placement names it from the point (bottom-start: the surface's block-start, inline-start corner on the point; bottom-end: its block-start, inline-end corner; top-start and top-end: its block-end corners); WHERE the side below or above the point has less room than the surface needs within comp.menu.surface.viewport-margin of the edge of the visible part of the window, above the on-screen keyboard while one is shown, and the other side has more THE menu SHALL flip to the other side of the point, and WHERE the side before or after the point along the line has less room than the surface's width and the other has more SHALL flip along the line as well, each axis by itself, so that a surface asked for near a corner of the window opens away from that corner with a corner of its own on the point; it SHALL then shift to stay within that margin, limit its height as MNU-2 says and scroll its items beyond it, and keep the point where it is in the area while the area moves. FOR a request by the keyboard THE menu SHALL place the surface against the element that had focus as MNU-2 places it against an anchor, offset included. WHEN open becomes true with no request since the menu was last closed THE menu SHALL place the surface against the area as MNU-2 says. The surface SHALL grow from its edge that faces the point or the element (MNU-4).
MNU-62 WHILE trigger is context THE menu SHALL behave as every other menu does once it is open — the same items, focus on opening (MNU-1), keys, typeahead, submenus, activation and close reasons — and WHEN it closes SHALL return focus, where focus was inside it, to the element that had focus when the request was made (the element a keyboard request was made on), and leave focus where the platform puts it where no element had it; Tab and Shift+Tab SHALL move focus on from that element. A press anywhere outside the menu's surfaces, the area included, SHALL count as a press outside (MNU-5); a secondary click or a long press elsewhere while the menu is open SHALL therefore close it and, in the area, request it again at the new point.
MNU-63 WHILE trigger is context THE menu SHALL NOT expose on its area that it opens a menu, whether it is open or which element it is (no aria-haspopup, aria-expanded or aria-controls on the web), because the area is not a button: a context menu is found by the platform's own convention — a secondary click, a long press, Shift+F10, the ContextMenu key, the context-menu command of assistive technology. THE menu SHALL NOT be named by its area; it SHALL take the name the product gives it (aria-label on the web), and have none where the product gives none, as the platform's own context menus have none.
MNU-64 THE menu SHALL draw the rows of every surface at its density. WHILE density is regular each row SHALL take the regular value of every token of the density axis this spec names (comp.menu.container.height.regular, comp.menu.container.padding-block.regular, comp.menu.container.padding-inline.regular, comp.menu.container.gap.regular, comp.menu.leading-icon.size.regular, comp.menu.trailing-icon.size.regular, comp.menu.mark.size.regular, comp.menu.group-title.padding-inline.regular); WHILE density is compact it SHALL take the compact value of each in its place (comp.menu.container.height.compact, comp.menu.container.padding-block.compact, comp.menu.container.padding-inline.compact, comp.menu.container.gap.compact, comp.menu.leading-icon.size.compact, comp.menu.trailing-icon.size.compact, comp.menu.mark.size.compact, comp.menu.group-title.padding-inline.compact), so that a compact row is lower, set in less from its edges and draws smaller glyphs, the room a row keeps for an empty column (MNU-59) shrinking with them. The text styles, the surface and its paddings, the dividers, the group gap and the control of a checkbox or radio item SHALL be the same in both. Every submenu SHALL take the menu's density. WHEN density names a value the active design system does not have THE menu SHALL draw the default density's rows and in debug builds report the unknown value; a density a design system added SHALL be drawn from that density's tokens, as the core values are. WHEN density changes while the menu is open THE menu SHALL draw the new rows at once, without a motion, keeping focus where it is.
MNU-65 WHEN the element the open menu is placed against — the anchor; for a context menu its area, or the element a keyboard request was made on (MNU-61) — is no longer drawn: taken out of the page, given no box (on the web display none on it or on an ancestor), made invisible (visibility hidden) or skipped by its platform's content visibility (on the web content-visibility hidden, or auto while it is off screen), THE menu SHALL emit close once with the reason anchor-hidden and be closed as MNU-5 says, every open submenu with it (MNU-49), and SHALL NOT place its surface against that element while it is not drawn, because a surface with nothing beside it no longer says what it belongs to. WHEN it closes with focus inside it THE menu SHALL return focus as MNU-49 says and, where that element cannot take focus, SHALL NOT keep focus inside its closed surfaces. WHEN the element is drawn again while the menu is still open THE menu SHALL follow it again, and emit close again the next time it is no longer drawn.
MNU-66 WHERE the platform has ARIA attributes (the web), WHILE an item is a link item its role is carried by its anchor (MNU-53), and WHEN the product sets an aria- attribute on its host, by the attribute or by the ARIA property that reflects it, THE menu SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on that anchor — an id reference by the elements it names in the host's tree or a tree around it, a description after its own — except those the item owns (aria-disabled, aria-current, aria-keyshortcuts), which it SHALL move, not expose and report in debug builds; its label is its name over a name given so, and while label is empty a name given so is its name. WHEN the item stops being a link item THE menu SHALL give back to its host what it moved, as it was written, and WHEN it becomes one SHALL move what the host carries. WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE menu SHALL take it off the anchor.
MNU-67 WHERE the platform has ARIA attributes (the web), the hosts of the menu, of an item that is not a link item (a checkbox item, a radio item and a submenu item included) and of a group carry their roles themselves (MNU-24), so what the product writes on them is not moved, but for a name: WHILE an item's or a group's label, or the anchor that names a menu, is not empty THE menu SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that its own is the whole name although the platform gives aria-labelledby the precedence, and WHEN it is emptied SHALL give them back as they were written; WHILE it is empty a name the product gives so is the name and an item SHALL NOT be reported as having none in debug builds (MNU-26) — a context menu is named so (MNU-63). WHILE a menu is closed, or a group is in a group (MNU-29), it exposes nothing, and THE menu SHALL hold them as well, giving them back when it is exposed again. WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it to null, THE menu SHALL take it away.
MNU-68 WHEN a surface of the menu is shown — the menu's own as it opens, a submenu's as its item opens it — THE menu SHALL show that surface's list from its start: a list taller than the surface (MNU-2, MNU-46) SHALL open scrolled to its top, so that its first row, its first group's title included, is in view, and WHERE the item that takes focus (MNU-1, MNU-43, or the last enabled item a menubar opens its menu on, MBR-9) is not whole in view there, scrolled just far enough to bring that item whole into view and no further. WHEN focus moves to an item by a key or by the menu's own doing (MNU-7, MNU-48) THE menu SHALL scroll the list that holds it just far enough to bring the item whole into view, and not at all where it is whole in view already — the first item of the list together with the list's start, the last together with its end, and the first item of a titled group together with that title (MNU-28); focus that follows the pointer (MNU-8) SHALL scroll nothing. Whether the item is in view SHALL be judged as the surface stands at rest: while its opening motion runs (MNU-4) the list SHALL keep its scroll position until the surface stands at rest, and then bring the item that has focus into view as this sentence says, so that no frame of the motion moves the list.
MNU-69 THE menu SHALL break the lines of an item's label, supporting text and trailing text and of a group's title by the rules of the text's language: a line of Japanese or Chinese SHALL neither begin with closing punctuation, a small kana, the prolonged sound mark (ー) or an iteration mark nor end with opening punctuation (the strict rules of line breaking that JIS X 4051 and the W3C's requirements for Japanese text layout set out), whether or not the product marks the text's language; and WHERE the platform finds the phrases of Japanese text (on the web, word-break: auto-phrase, which reads the language the product marks), a line of Japanese SHALL break between two phrases rather than inside one. A phrase or a word longer than its line SHALL still break rather than overflow (MNU-20).

プラットフォーム別の名前対応

パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。

仕様 Web SwiftUI Jetpack Compose Flutter React Native
コンポーネント tanrei-menu TanreiMenu TanreiMenu TanreiMenuAnchor Menu
open · プロパティ open isPresented expanded controller visible
placement · プロパティ placement placement placement placement placement
trigger · プロパティ trigger trigger trigger trigger trigger
separation · プロパティ separation separation separation separation separation
iconSpace · プロパティ iconSpace iconSpace iconSpace iconSpace iconSpace
density · プロパティ density density density density density
close · イベント close isPresented onDismissRequest onClose onDismiss
contextRequest · イベント contextrequest onContextRequest onContextRequest onContextRequest onContextRequest
備考 tanrei-menu (open, placement, separation, and anchor: the id of the anchor element, as the HTML anchor attribute) holds tanrei-menu-item, tanrei-menu-checkbox-item, tanrei-menu-radio-item, tanrei-submenu and tanrei-menu-group children; item attributes are kebab-case (supporting-text, leading-icon, trailing-icon, trailing-text, mark-icon). click, change and toggle fire on the item or group and bubble, so one listener on the menu can take them all. The surface is a manual popover in the top layer, placed with CSS anchor positioning and a script fallback where the browser has none; each submenu's surface is a popover of its own, placed against its tanrei-submenu the same way. The menu implements its own light dismiss so that it can emit close with a reason, and treats every surface of its tree as inside. A press outside also reaches the element under it. It sets aria-haspopup="menu", aria-controls and aria-expanded on the anchor element, and the same three on each submenu item's row. The inline attribute, for tests, draws the open surface in place, with each open submenu beside it (MNU-19). The mark is the mark part: the box or the ring of a control look, drawn by the menu's own styles from --tanrei-comp-menu-control-*, or the icon helper's glyph (MNU-22, MNU-38). The submenu indicator is the icon helper's chevron_right in the submenu-indicator part. A group's title is inset by --tanrei-comp-menu-group-title-padding-inline-regular from the rows' edges (MNU-28). The link item is not built on this platform yet: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. A link item's current writes aria-current="page" on its anchor and draws the row from the current condition (MNU-58). Under gap each group, and each run of items outside a group, is a .tile (part tile) of its own inside the scroll box; under divider the tiles are boxes with no look (MNU-36). trigger is the trigger attribute; with context, anchor names the area. contextRequest is contextrequest, a cancelable event whose detail is { x, y, element, input }: client coordinates, the element under the pointer or with focus, and pointer, long-press or keyboard. The menu listens on the area for contextmenu (a secondary click, Android's long press, the context-menu command of assistive technology) and cancels it unless the product cancels the request; for a touch or a pen held still, which WebKit on iOS answers with no contextmenu; and for Shift+F10 and the ContextMenu key on keydown, whose own contextmenu it then cancels. After a long press the release of that press, and its click, go no further. On iOS a long press may also select text: an area whose text is not to be selected says so in its own styles (user-select, -webkit-touch-callout). A modifier on the anchor, .tanreiMenu(isPresented:placement:) { … }, as .popover, presented with presentationCompactAdaptation(.popover) so it stays a popover on iPhone; a press outside does not reach what is under it. The content builder takes the item views. disabled follows .disabled(_:). Closing writes false to isPresented, which is close. SwiftUI's own Menu is not used: its items cannot be tokenised. Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only. Compose idiom, as DropdownMenu: TanreiMenu(expanded, onDismissRequest, placement) { TanreiMenuItem(text, onClick, …); TanreiMenuGroup { … } } inside the anchor's Box, shown in a Popup; the item composables are called in its content lambda. The caller closes it, so close is onDismissRequest and it carries the reason; the caller also keeps each checkbox item's checked (state hoisting). A press outside does not reach what is under it. enabled is the inverse flag. Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only. Flutter idiom, as MenuAnchor: TanreiMenuAnchor(controller:, placement:, menuChildren: […], builder:) shown in the Overlay; the item widgets go in menuChildren. MenuController.open() and .close() are open, onClose is close. A press outside also reaches what is under it (consumeOutsideTap: false). Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only. <Menu visible anchorRef placement onDismiss items={[…]}>: items is an array whose element type is the union of the five kinds, told apart by a kind field ('item', 'checkbox-item', 'radio-item', 'submenu', 'group'). Presented on the overlay layer that TanreiProvider renders (not a Modal, which would make the rest of the screen unreachable) and placed by measuring the anchor (measureInWindow); Android's back button (BackHandler) is close. A press outside closes the menu and does not reach what is under it. Controlled: the caller sets visible to false from onDismiss and keeps each checkbox item's checked and each group's value. Ships the previous revision today (the check stroked as a path in a 16 box, a group's title starting where the labels start). Not built on this platform yet, among the rest of this spec: the submenu item kind and everything about it (MNU-42 to MNU-52), separation (MNU-35, MNU-36), the checkbox and radio looks of the mark and markIcon (MNU-38 to MNU-41) and their 17 fixtures; the selected fill and colors arrive through the tokens, the fill of a checked row with them: none at standard contrast in the default system, as MNU-37 says. Reading comp.menu.separation, comp.menu.checkbox-item.mark and comp.menu.radio-item.mark as typed choices needs this platform's token output to write a component's choice as a typed enumeration, which it does not yet. Nor is the link item: MNU-53 to MNU-57 and what MNU-6, MNU-19 and MNU-24 say of it, with the fixtures links, links-rtl and links-trailing. Lagging behind MNU-58 (a link item's current, drawn with the selected fill) and MNU-36 (the groups as separate tiles under gap): it has neither yet. Not built yet: iconSpace (MNU-59) — this platform still keeps the mark and leading icon columns in every row of a surface where any item draws in them. Not built yet: the context trigger (MNU-60 to MNU-63) — this platform opens the menu from an anchor only.

トークン

トークン 参照先 解決値(ライト)
--tanrei-comp-menu-checkbox-item-mark — 0
--tanrei-comp-menu-container-gap-compact comp.tree-view.container.gap.compact 8px
--tanrei-comp-menu-container-gap-regular sys.space.space150 12px
--tanrei-comp-menu-container-height-compact comp.tree-view.container.height.compact 32px
--tanrei-comp-menu-container-height-regular — 48px
--tanrei-comp-menu-container-padding-block-compact comp.tree-view.container.padding-block.compact 6px
--tanrei-comp-menu-container-padding-block-regular — 8px
--tanrei-comp-menu-container-padding-inline-compact comp.tree-view.container.padding-inline.compact 8px
--tanrei-comp-menu-container-padding-inline-regular sys.space.space150 12px
--tanrei-comp-menu-container-shape — 4px
--tanrei-comp-menu-control-box-shape — 2px
--tanrei-comp-menu-control-box-size — 18px
--tanrei-comp-menu-control-fill-inset — 0px
--tanrei-comp-menu-control-fill-opacity sys.state.selected-state-layer-opacity 0.16
--tanrei-comp-menu-control-mark-size sys.icon.small 20px
--tanrei-comp-menu-control-mark-weight sys.icon.mark.weight 500
--tanrei-comp-menu-control-outline-width sys.border.thick 2px
--tanrei-comp-menu-control-ring-selected-width-share — 0.5
--tanrei-comp-menu-control-ring-size — 20px
--tanrei-comp-menu-control-ring-width sys.border.thick 2px
--tanrei-comp-menu-current-container-color sys.color.secondary-container #262626 · 16%
--tanrei-comp-menu-current-control-mark-color — #000000 · 0%
--tanrei-comp-menu-current-control-color — #000000 · 0%
--tanrei-comp-menu-current-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-menu-current-disabled-control-mark-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-control-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-mark-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-current-disabled-trailing-text-color — #1b1b1b · 38%
--tanrei-comp-menu-current-focused-container-color — #262626 · 24.4%
--tanrei-comp-menu-current-focused-control-mark-color — #000000 · 0%
--tanrei-comp-menu-current-focused-control-color — #000000 · 0%
--tanrei-comp-menu-current-focused-label-color — #262626
--tanrei-comp-menu-current-focused-leading-icon-color — #262626
--tanrei-comp-menu-current-focused-mark-color — #000000 · 0%
--tanrei-comp-menu-current-focused-state-layer-color — #262626
--tanrei-comp-menu-current-focused-supporting-text-color — #262626
--tanrei-comp-menu-current-focused-trailing-icon-color — #262626
--tanrei-comp-menu-current-focused-trailing-text-color — #262626
--tanrei-comp-menu-current-hovered-container-color — #262626 · 22.7%
--tanrei-comp-menu-current-hovered-control-mark-color — #000000 · 0%
--tanrei-comp-menu-current-hovered-control-color — #000000 · 0%
--tanrei-comp-menu-current-hovered-label-color — #262626
--tanrei-comp-menu-current-hovered-leading-icon-color — #262626
--tanrei-comp-menu-current-hovered-mark-color — #000000 · 0%
--tanrei-comp-menu-current-hovered-state-layer-color — #262626
--tanrei-comp-menu-current-hovered-supporting-text-color — #262626
--tanrei-comp-menu-current-hovered-trailing-icon-color — #262626
--tanrei-comp-menu-current-hovered-trailing-text-color — #262626
--tanrei-comp-menu-current-label-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-current-leading-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-current-mark-color — #000000 · 0%
--tanrei-comp-menu-current-pressed-container-color — #262626 · 24.4%
--tanrei-comp-menu-current-pressed-control-mark-color — #000000 · 0%
--tanrei-comp-menu-current-pressed-control-color — #000000 · 0%
--tanrei-comp-menu-current-pressed-label-color — #262626
--tanrei-comp-menu-current-pressed-leading-icon-color — #262626
--tanrei-comp-menu-current-pressed-mark-color — #000000 · 0%
--tanrei-comp-menu-current-pressed-state-layer-color — #262626
--tanrei-comp-menu-current-pressed-supporting-text-color — #262626
--tanrei-comp-menu-current-pressed-trailing-icon-color — #262626
--tanrei-comp-menu-current-pressed-trailing-text-color — #262626
--tanrei-comp-menu-current-state-layer-color comp.menu.current.label.color #262626
--tanrei-comp-menu-current-supporting-text-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-current-trailing-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-current-trailing-text-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-destructive-container-color — #000000 · 0%
--tanrei-comp-menu-destructive-control-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-control-color — #000000 · 0%
--tanrei-comp-menu-destructive-disabled-container-color — #000000 · 0%
--tanrei-comp-menu-destructive-disabled-control-mark-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-disabled-control-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-disabled-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-disabled-trailing-text-color — #1b1b1b · 38%
--tanrei-comp-menu-destructive-focused-container-color — #cd262f · 10%
--tanrei-comp-menu-destructive-focused-control-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-focused-control-color — #000000 · 0%
--tanrei-comp-menu-destructive-focused-label-color — #cd262f
--tanrei-comp-menu-destructive-focused-leading-icon-color — #cd262f
--tanrei-comp-menu-destructive-focused-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-focused-state-layer-color — #cd262f
--tanrei-comp-menu-destructive-focused-supporting-text-color — #4b4b4b
--tanrei-comp-menu-destructive-focused-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-destructive-focused-trailing-text-color — #4b4b4b
--tanrei-comp-menu-destructive-hovered-container-color — #cd262f · 8%
--tanrei-comp-menu-destructive-hovered-control-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-hovered-control-color — #000000 · 0%
--tanrei-comp-menu-destructive-hovered-label-color — #cd262f
--tanrei-comp-menu-destructive-hovered-leading-icon-color — #cd262f
--tanrei-comp-menu-destructive-hovered-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-hovered-state-layer-color — #cd262f
--tanrei-comp-menu-destructive-hovered-supporting-text-color — #4b4b4b
--tanrei-comp-menu-destructive-hovered-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-destructive-hovered-trailing-text-color — #4b4b4b
--tanrei-comp-menu-destructive-label-color sys.color.error #cd262f
--tanrei-comp-menu-destructive-leading-icon-color sys.color.error #cd262f
--tanrei-comp-menu-destructive-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-pressed-container-color — #cd262f · 10%
--tanrei-comp-menu-destructive-pressed-control-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-pressed-control-color — #000000 · 0%
--tanrei-comp-menu-destructive-pressed-label-color — #cd262f
--tanrei-comp-menu-destructive-pressed-leading-icon-color — #cd262f
--tanrei-comp-menu-destructive-pressed-mark-color — #000000 · 0%
--tanrei-comp-menu-destructive-pressed-state-layer-color — #cd262f
--tanrei-comp-menu-destructive-pressed-supporting-text-color — #4b4b4b
--tanrei-comp-menu-destructive-pressed-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-destructive-pressed-trailing-text-color — #4b4b4b
--tanrei-comp-menu-destructive-state-layer-color comp.menu.destructive.label.color #cd262f
--tanrei-comp-menu-destructive-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-destructive-trailing-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-destructive-trailing-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-disabled-container-shape — 4px
--tanrei-comp-menu-disabled-opacity — 1
--tanrei-comp-menu-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-menu-divider-space sys.space.space50 4px
--tanrei-comp-menu-divider-thickness sys.border.hairline 1px
--tanrei-comp-menu-expanded-container-color sys.color.surface-container-high #1b1b1b · 8%
--tanrei-comp-menu-expanded-control-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-control-color — #000000 · 0%
--tanrei-comp-menu-expanded-disabled-container-color — #1b1b1b · 8%
--tanrei-comp-menu-expanded-disabled-control-mark-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-control-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-mark-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-disabled-trailing-text-color — #1b1b1b · 38%
--tanrei-comp-menu-expanded-focused-container-color — #1b1b1b · 10%
--tanrei-comp-menu-expanded-focused-control-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-focused-control-color — #000000 · 0%
--tanrei-comp-menu-expanded-focused-label-color — #1b1b1b
--tanrei-comp-menu-expanded-focused-leading-icon-color — #4b4b4b
--tanrei-comp-menu-expanded-focused-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-focused-state-layer-color — #1b1b1b
--tanrei-comp-menu-expanded-focused-supporting-text-color — #4b4b4b
--tanrei-comp-menu-expanded-focused-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-expanded-focused-trailing-text-color — #4b4b4b
--tanrei-comp-menu-expanded-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-menu-expanded-hovered-control-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-hovered-control-color — #000000 · 0%
--tanrei-comp-menu-expanded-hovered-label-color — #1b1b1b
--tanrei-comp-menu-expanded-hovered-leading-icon-color — #4b4b4b
--tanrei-comp-menu-expanded-hovered-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-hovered-state-layer-color — #1b1b1b
--tanrei-comp-menu-expanded-hovered-supporting-text-color — #4b4b4b
--tanrei-comp-menu-expanded-hovered-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-expanded-hovered-trailing-text-color — #4b4b4b
--tanrei-comp-menu-expanded-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-menu-expanded-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-expanded-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-menu-expanded-pressed-control-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-pressed-control-color — #000000 · 0%
--tanrei-comp-menu-expanded-pressed-label-color — #1b1b1b
--tanrei-comp-menu-expanded-pressed-leading-icon-color — #4b4b4b
--tanrei-comp-menu-expanded-pressed-mark-color — #000000 · 0%
--tanrei-comp-menu-expanded-pressed-state-layer-color — #1b1b1b
--tanrei-comp-menu-expanded-pressed-supporting-text-color — #4b4b4b
--tanrei-comp-menu-expanded-pressed-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-expanded-pressed-trailing-text-color — #4b4b4b
--tanrei-comp-menu-expanded-state-layer-color comp.menu.expanded.label.color #1b1b1b
--tanrei-comp-menu-expanded-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-expanded-trailing-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-expanded-trailing-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-focused-container-shape — 4px
--tanrei-comp-menu-focused-opacity — 1
--tanrei-comp-menu-group-title-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-group-title-padding-inline-compact comp.menu.container.padding-inline.compact 8px
--tanrei-comp-menu-group-title-padding-inline-regular comp.menu.container.padding-inline.regular 12px
--tanrei-comp-menu-group-gap sys.space.space200 16px
--tanrei-comp-menu-hovered-container-shape — 4px
--tanrei-comp-menu-hovered-opacity — 1
--tanrei-comp-menu-leading-icon-size-compact sys.icon.small 20px
--tanrei-comp-menu-leading-icon-size-regular sys.icon.medium 24px
--tanrei-comp-menu-mark-size-compact sys.icon.small 20px
--tanrei-comp-menu-mark-size-regular sys.icon.medium 24px
--tanrei-comp-menu-mark-width sys.border.thick 2px
--tanrei-comp-menu-motion-close-duration sys.motion.role.appear.anchored.exit 250ms
--tanrei-comp-menu-motion-close-easing sys.motion.easing.exit cubic-bezier(0.3, 0, 1, 1)
--tanrei-comp-menu-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-menu-motion-open-duration sys.motion.role.appear.anchored.enter 300ms
--tanrei-comp-menu-motion-open-easing sys.motion.easing.enter cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-menu-motion-state-duration sys.motion.role.state.large 250ms
--tanrei-comp-menu-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-menu-motion-submenu-delay sys.motion.duration.short4 200ms
--tanrei-comp-menu-pressed-container-shape — 4px
--tanrei-comp-menu-pressed-opacity — 1
--tanrei-comp-menu-radio-item-mark — 0
--tanrei-comp-menu-selected-container-color sys.color.secondary-container #262626 · 0%
--tanrei-comp-menu-selected-control-mark-color sys.color.on-primary #ffffff
--tanrei-comp-menu-selected-control-color sys.color.primary #1b1b1b
--tanrei-comp-menu-selected-disabled-container-color — #1b1b1b · 0%
--tanrei-comp-menu-selected-disabled-control-mark-color — #ffffff
--tanrei-comp-menu-selected-disabled-control-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-disabled-mark-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-disabled-trailing-text-color — #1b1b1b · 38%
--tanrei-comp-menu-selected-focused-container-color — #262626 · 10%
--tanrei-comp-menu-selected-focused-control-mark-color — #ffffff
--tanrei-comp-menu-selected-focused-control-color — #1b1b1b
--tanrei-comp-menu-selected-focused-label-color — #262626
--tanrei-comp-menu-selected-focused-leading-icon-color — #262626
--tanrei-comp-menu-selected-focused-mark-color — #1b1b1b
--tanrei-comp-menu-selected-focused-state-layer-color — #262626
--tanrei-comp-menu-selected-focused-supporting-text-color — #262626
--tanrei-comp-menu-selected-focused-trailing-icon-color — #262626
--tanrei-comp-menu-selected-focused-trailing-text-color — #262626
--tanrei-comp-menu-selected-hovered-container-color — #262626 · 8%
--tanrei-comp-menu-selected-hovered-control-mark-color — #ffffff
--tanrei-comp-menu-selected-hovered-control-color — #1b1b1b
--tanrei-comp-menu-selected-hovered-label-color — #262626
--tanrei-comp-menu-selected-hovered-leading-icon-color — #262626
--tanrei-comp-menu-selected-hovered-mark-color — #1b1b1b
--tanrei-comp-menu-selected-hovered-state-layer-color — #262626
--tanrei-comp-menu-selected-hovered-supporting-text-color — #262626
--tanrei-comp-menu-selected-hovered-trailing-icon-color — #262626
--tanrei-comp-menu-selected-hovered-trailing-text-color — #262626
--tanrei-comp-menu-selected-label-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-selected-leading-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-selected-mark-color sys.color.primary #1b1b1b
--tanrei-comp-menu-selected-pressed-container-color — #262626 · 10%
--tanrei-comp-menu-selected-pressed-control-mark-color — #ffffff
--tanrei-comp-menu-selected-pressed-control-color — #1b1b1b
--tanrei-comp-menu-selected-pressed-label-color — #262626
--tanrei-comp-menu-selected-pressed-leading-icon-color — #262626
--tanrei-comp-menu-selected-pressed-mark-color — #1b1b1b
--tanrei-comp-menu-selected-pressed-state-layer-color — #262626
--tanrei-comp-menu-selected-pressed-supporting-text-color — #262626
--tanrei-comp-menu-selected-pressed-trailing-icon-color — #262626
--tanrei-comp-menu-selected-pressed-trailing-text-color — #262626
--tanrei-comp-menu-selected-state-layer-color comp.menu.selected.label.color #262626
--tanrei-comp-menu-selected-supporting-text-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-selected-trailing-icon-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-selected-trailing-text-color sys.color.on-secondary-container #262626
--tanrei-comp-menu-separation sys.behavior.separation 0
--tanrei-comp-menu-shortcut-variant — 0
--tanrei-comp-menu-submenu-offset sys.space.space50 4px
--tanrei-comp-menu-surface-color sys.color.surface-bright #ffffff
--tanrei-comp-menu-surface-elevation sys.elevation.level2 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-menu-surface-max-width — 320px
--tanrei-comp-menu-surface-min-width — 160px
--tanrei-comp-menu-surface-offset sys.space.space50 4px
--tanrei-comp-menu-surface-padding-block sys.space.space50 4px
--tanrei-comp-menu-surface-padding-inline sys.space.space50 4px
--tanrei-comp-menu-surface-shape sys.shape.corner.medium 8px
--tanrei-comp-menu-surface-viewport-margin sys.space.space100 8px
--tanrei-comp-menu-trailing-icon-size-compact sys.icon.small 20px
--tanrei-comp-menu-trailing-icon-size-regular sys.icon.medium 24px
--tanrei-comp-menu-unselected-container-color — #000000 · 0%
--tanrei-comp-menu-unselected-control-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-control-color sys.color.outline #848484
--tanrei-comp-menu-unselected-disabled-container-color — #000000 · 0%
--tanrei-comp-menu-unselected-disabled-control-mark-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-disabled-control-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-disabled-leading-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-disabled-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-disabled-trailing-icon-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-disabled-trailing-text-color — #1b1b1b · 38%
--tanrei-comp-menu-unselected-focused-container-color — #1b1b1b · 10%
--tanrei-comp-menu-unselected-focused-control-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-focused-control-color — #848484
--tanrei-comp-menu-unselected-focused-label-color — #1b1b1b
--tanrei-comp-menu-unselected-focused-leading-icon-color — #4b4b4b
--tanrei-comp-menu-unselected-focused-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-focused-state-layer-color — #1b1b1b
--tanrei-comp-menu-unselected-focused-supporting-text-color — #4b4b4b
--tanrei-comp-menu-unselected-focused-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-unselected-focused-trailing-text-color — #4b4b4b
--tanrei-comp-menu-unselected-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-menu-unselected-hovered-control-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-hovered-control-color — #848484
--tanrei-comp-menu-unselected-hovered-label-color — #1b1b1b
--tanrei-comp-menu-unselected-hovered-leading-icon-color — #4b4b4b
--tanrei-comp-menu-unselected-hovered-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-hovered-state-layer-color — #1b1b1b
--tanrei-comp-menu-unselected-hovered-supporting-text-color — #4b4b4b
--tanrei-comp-menu-unselected-hovered-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-unselected-hovered-trailing-text-color — #4b4b4b
--tanrei-comp-menu-unselected-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-menu-unselected-leading-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-unselected-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-menu-unselected-pressed-control-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-pressed-control-color — #848484
--tanrei-comp-menu-unselected-pressed-label-color — #1b1b1b
--tanrei-comp-menu-unselected-pressed-leading-icon-color — #4b4b4b
--tanrei-comp-menu-unselected-pressed-mark-color — #000000 · 0%
--tanrei-comp-menu-unselected-pressed-state-layer-color — #1b1b1b
--tanrei-comp-menu-unselected-pressed-supporting-text-color — #4b4b4b
--tanrei-comp-menu-unselected-pressed-trailing-icon-color — #4b4b4b
--tanrei-comp-menu-unselected-pressed-trailing-text-color — #4b4b4b
--tanrei-comp-menu-unselected-state-layer-color comp.menu.unselected.label.color #1b1b1b
--tanrei-comp-menu-unselected-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-unselected-trailing-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-unselected-trailing-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-menu-group-title-typescale sys.typescale.label-small 500 12px/1.3333
--tanrei-comp-menu-label-typescale sys.typescale.label 500 14px/1.4286
--tanrei-comp-menu-supporting-text-typescale sys.typescale.body-small 400 13px/1.5385
--tanrei-comp-menu-trailing-text-typescale sys.typescale.label-small 500 12px/1.3333

フィクスチャ

基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。

生成元 specs/menu.spec.json, tokens/comp/menu.tokens.json, design/tokens/

ガイドライン

使う場面

画面に常に置いておくほどではない、補助的なアクションや設定に使います。行の「その他」、ツールバーからあふれた操作、並べ替えの順序、表示のオプションなどです。開いて 1 つ選び、元の作業に戻る、という使われ方をします。

画面の主なアクションにはボタンを使います。メニューに入れると隠れてしまうからです。選ぶこと自体が画面の主な目的なら、リスト、ラジオボタン、セレクトを使います。答えないと先に進めない問いにはダイアログを使います。メニューに入れるのはメニューの項目だけです。入力欄、スライダー、スイッチは、メニューが必要とするキー操作を奪ってしまうので入れません。

どこかへ移動する行はリンク項目です。項目に href を指定します。パンくずリストの折りたたまれた階層、言語の選択、「その他」のように、求められるまで表に出ない少数の行き先に使います。常に見えているサイトの主要なページはナビゲーションです。ナビゲーションバー、ナビゲーションドロワー、またはリンクのリストに置きます。

項目の種類の選び方

種類 何か 実行すると 用途
Item アクション。href があればリンク クリックを発火し、メニューが閉じます。リンク項目の行き先は、そのあとで開きます。 「コピー」「名前を変更」「共有」。「削除」「破棄」には destructive を付けると、エラーの色で描かれます。行き先にするには href を指定します。
Checkbox item それぞれ独立してオンとオフを切り替える選択肢 checked を切り替え、change を発火します。 「グリッドを表示」「ルーラーを表示」など、互いに影響しない設定。
Radio item グループの中の選択肢の 1 つ グループの value をその値にし、グループが change を発火します。すでにチェックされているラジオ項目は何も発火しません。 「名前順」「日付順」など、1 つの問いへの 1 つの答え。
Submenu 隣にもう 1 つの階層を開く項目 サブメニューを開き、toggle を発火します。メニューは開いたままです。 「最近使った項目を開く」「共有」「移動」など、そのまま並べるとメニューが長くなりすぎる、もうひとまとまりの項目。
Group 関連する項目のまとまり。タイトルを付けられます 何も起きません。フォーカスも受けません。 関連する項目を区切ることと、1 つの問いのラジオ項目をまとめること。

チェックボックス項目とラジオ項目は、タップ、クリック、Enter で実行するとアクションと同じくメニューを閉じます。Space で実行したときは開いたままなので、キーボードで続けて複数の設定を変えられます。

グループ、タイトル、分け方

  • メニューは、グループとその隣にあるものを、区切り線か余白で分けます。一番上と一番下は分けません。余白で分けるときは、各グループと、グループの外にひと続きに並ぶ項目が、それぞれ独立したシートになり、シートの間には何もない空間が空きます。利用者側で区切り線やシートを置く必要はありません。
  • どちらで分けるかはデザインシステムの選択で、自分のパーツを分けるすべてのコンポーネントに共通の 1 つの選択です。既定のシステムでは区切り線です。separation は、1 つのメニューとそのサブメニューについて決めます。auto(既定)はデザインシステムに従い、divider と gap はシステムの選択にかかわらずその分け方にします。余白で分けるときはセパレーターを伝えず、グループはグループとして伝えます。
  • title を付けたグループは、項目の上の、行の内容が始まる位置から控えめな見出しを描き、それがグループの名前になります。ラジオ項目のグループには、「並べ替え」のように問いを表すタイトルを付けます。
  • ラジオ項目はグループに入れます。グループの外のラジオ項目はチェックされることがなく、デバッグビルドで報告されます。
  • グループの中にグループは入れられません。内側のグループの項目は、外側のグループの項目として描かれます。

印とアイコンの列

チェックボックス項目とラジオ項目の印は行の先頭の印の列に、先頭アイコンはその後ろのアイコンの列に描きます。何も描かない列の幅を、どの行で空けておくかを決めるのが iconSpace です。

  • auto(既定)は、セクションごとに決めます。セクションとは、グループか、グループの外にひと続きに並ぶ項目のことです。同じセクションのほかの項目がその列に何かを描いている間は、行はその列の幅を空けておきます。そのため、セクションの中のラベルは一直線に始まります。印もアイコンもないセクションは、前に空きを置かず、行の内容が始まる位置からラベルを始めます。
  • always は、すべての行で両方の列を空けておきます。メニューのすべてのラベルが一直線に始まります。
  • never は、その列に何かを描く行にだけ列を設けます。各ラベルは、その行自身の印やアイコンのすぐ後から始まります。

サーフェスごとに決め、サブメニューはそのメニューの iconSpace に従います。グループのタイトルは、行が何を空けておくかにかかわらず、行の内容が始まる位置から始まります。ツリービューとナビゲーションドロワーにも、同じ値と既定値の iconSpace があります。

密度

density は行が取る広さを決めます。regular(既定。行の高さ 48、グリフ 24)は、タッチでもポインターでも使われるプロダクト向けです。compact(行の高さ 32、グリフ 20。ツリービューの compact の行)は、マウスで使い、長いメニューを何度も開くツール向けです。文字の大きさはどちらでも変わりません。タッチでは行がタッチターゲットより低くなることはなく、スマートフォンの compact のメニューも行は 48 のままです。サブメニューはすべて、そのメニューの密度に従います。

リンク

リンク項目はプラットフォーム自身のリンクです。ブラウザーのコンテキストメニュー、新しいタブで開く操作、ホバーしたときに表示されるアドレスは、どれもそのまま働きます。リンク独自の見た目はありません。どの状態でも、隣の行のアクションと同じに描かれます。

  • 操作。 クリック、タップ、Enter、Space のどれでも、項目の click を発火し、メニューが閉じ、そのあとで行き先が開きます。Space は、メニューのほかのすべての行と同じように、リンク項目も実行します。メニューの外では、Space はリンクを実行しません。
  • ルーター。 自分で移動を扱うには、項目の click をキャンセルします。メニューはそれでも閉じ、ほかには何も開きません。
  • ほかのタブ。 修飾キーを押しながらのクリックや Enter、中クリックは、ブラウザーのものです。メニューは close を発火せず、開いたままなので、メニューのリンクを続けていくつも開けます。
  • 新しいコンテキスト。 target: new は新しいタブやウィンドウで開きます。trailingIcon に自分のグリフを指定していなければ、項目の末尾のアイコンの列にアイコンセットの open_in_new が描かれ、targetLabel がそれを支援技術に伝えます。ショートカットや末尾のテキストは、その印の手前にある自分の列に残ります。
  • 今いるページ。 current は、利用者が今いるページのリンク項目を示します。現在のページとして伝え(Web では aria-current="page")、どのコントラストのスキームでも専用の塗りで描きます。チェックは付けず、ラベルも太くしません。利用者が選んだものではなく、利用者がどこにいるかを示すからです。指定するのは利用者側で、メニューが付けることはありません。href がなければ無視されます。
  • 無効。 無効なリンク項目は、どの方法でもたどれません。ほかの無効な項目と同じように、矢印キーでは到達できます。
  • 項目の種類は Item だけ。 チェックボックス項目、ラジオ項目、サブメニュー項目は行き先を持ちません。状態を持つか、別の階層を開くからです。

サブメニュー

サブメニュー項目は、末尾にアイコンセットの chevron_right を描く行で、その子が次の階層になります。子には、グループを含めてどの種類の項目も置けます。サブメニュー項目はアクションではなく、ショートカットも持ちません。

  • 開く。 押す、タップする、Enter、Space、行の終わりの方を指す矢印キーのどれでも、サブメニューがすぐに開き、フォーカスが最初の有効な項目へ移ります。ポインターを項目の上に置いたままにすると、少し待ってから開き、フォーカスは項目に残ります。
  • サブメニューへ向かう途中。 ポインターが項目から開いたサブメニューへ向かって動いている間、途中で通る項目はホバーにもフォーカスにもなりません。斜めに動かしても、サブメニューは閉じません。
  • 1 つの階層を閉じる。 行の始まりの方を指す矢印キーか Escape で、フォーカスのある階層が閉じ、フォーカスがその項目へ戻ります。Escape がメニュー全体を閉じるのは、サブメニューが開いていないときだけです。親の階層のほかの項目へ移っても、サブメニューは閉じます。1 つのサーフェスで開けるサブメニューは 1 つまでです。
  • 全体を閉じる。 どの階層のアクションの実行でも、Tab でも、すべてのサーフェスの外を押しても、フォーカスがすべてのサーフェスから出ても、メニュー全体が閉じ、close を 1 回だけ発火します。
  • 置き場所。 その項目のあるサーフェスの隣、末尾側に、最初の行を項目の行の高さに合わせて置きます。そちらに場所がなければ反対側に置きます。どちらにも入らないほど狭いウィンドウでは、親のサーフェスに重ねて置き、親の一部を帯のように残します。その帯を押すと、サブメニューが閉じます。メニューが自分の中身をサブメニューの中身に置き換えることはありません。
  • open と toggle。 サブメニュー項目の open は、その項目自身のものです。人がサブメニューを開閉すると、メニューが open を設定し、項目が toggle を発火します。利用者側で open を変えたとき、メニュー全体が閉じたときは、toggle を発火しません。
  • 深さ。 サブメニューは、メニューの下 2 階層までにします。3 階層目も描きますが、デバッグビルドで報告します。項目のないサブメニューも報告します。
  • グループの value が届くのは、同じサーフェスにあるラジオ項目だけです。印とアイコンの列も、サーフェスごとに決めます。

コンテキストメニュー

コンテキストメニューは、利用者が指したものに当てはまるアクションを、指したその場所に出します。項目、キー、サブメニューは同じメニューのままで、ボタンからではなく領域のある位置で開きます。

  • 領域。 trigger を context にし、anchor に領域の id を指定します。キャンバス、リスト、表の行、文書などです。領域はボタンではないので、メニューはそこにポップアップの状態を書き込みません。
  • 求め方。 右クリック(Mac では Control キーを押しながらのクリックや 2 本指でのクリックも)、タッチやペンを動かさずに 0.5 秒押し続けること、フォーカスのある要素での Shift+F10 か ContextMenu キー、スクリーンリーダーのコンテキストメニューのコマンドです。メニューは、位置、求められた対象の要素、求め方(pointer、long-press、keyboard)を添えて contextRequest を発火します。アンカーから開くときと同じく、open はプロダクトが設定します。
  • 開く場所。 角をその位置に合わせ、間を空けずに開きます。既定では位置の下、行の終わりの方へ広がり、placement で向きを変えられます。ウィンドウの端の近くでは、軸ごとに位置の反対側へ開くので、位置を覆いません。ページがスクロールしても領域と一緒に動きます。キーで求められたときは、アンカーに対するのと同じく、フォーカスのある要素に合わせて開きます。
  • ブラウザー自身のメニュー。 メニューは、応じた要求ごとにブラウザーのコンテキストメニューを取り消します。メニューが扱わない場所(入力欄、リンク、選択したテキストなど)では、contextRequest を取り消すと、その要求をブラウザーに任せられます。
  • フォーカスと閉じ方。 フォーカスは最初の有効な項目へ移り、閉じると、メニューが求められたときにフォーカスを持っていた要素へ戻ります。メニューの外を押すと、領域の中でも閉じます。別の場所で右クリックすると、閉じてからその場所で改めて求めます。
  • 入れるもの。 指したものに当てはまるアクションだけを、よく使うものから並べます。そのどれもプロダクトの別の場所にも置きます。求め方を知らない人には、コンテキストメニューは見えないからです。
  • iOS では、長押しがその下のテキストも選択することがあります。領域のテキストを選択させないときは、そのスタイルで指定します(user-select: none、-webkit-touch-callout: none)。

開く、フォーカス、閉じる

メニューは、操作するオーバーレイですが、モーダルではありません。人がその中で操作するので、フォーカスを受け取り、閉じるときに返します。画面のほかの部分はふさぎません。

  • 開く。 アンカーが実行されたら、利用者側で open を設定します。サーフェスはプラットフォームのオーバーレイ層に現れ、placement の指定どおりアンカーに沿って置かれます。既定はアンカーの下で、先頭側の端をそろえます。その側に空きがなければ反対側に回り、画面からはみ出さないようにずれ、高すぎればスクロールし、アンカーが動けば付いていきます。
  • フォーカスが入る。 フォーカスは最初の有効な項目へ移ります。その後は矢印キー、Home、End、先頭文字で項目の間を移動します。端では折り返し、グループはまたいで進みます。ポインターを項目に載せるとフォーカスもそこへ移るので、キー操作はポインターのあった場所から続けられます。
  • モーダルではない。 フォーカスを閉じ込めず、画面のほかの部分を操作不能にせず、スクリムも描きません。Tab を押すとメニューから抜け、メニューは閉じます。
  • 閉じる。 メニューは、閉じるよう求めるときに理由を伝えます。理由の名前はどのプラットフォームでも同じで、表記だけがそれぞれの流儀に従います。
理由 いつ
escape Escape が押されたとき。
back Android の戻るボタンやジェスチャーなど、プラットフォームの「戻る」。
outside サーフェスとアンカーの外が押されたとき。コンテキストメニューでは、サーフェスの外ならどこでも(領域の中でも)。
tab Tab か Shift+Tab が押されたとき。フォーカスはその後、アンカーから押したのと同じように次へ進みます。コンテキストメニューでは、メニューが求められたときにフォーカスを持っていた要素から次へ進みます。
focus-out それ以外の方法でフォーカスがメニューの外へ出たとき。
item 項目が実行されたとき。
  • フォーカスが戻る。 メニューが閉じたとき、フォーカスがメニューの中にあれば、アンカーへ戻ります。コンテキストメニューは、メニューが求められたときにフォーカスを持っていた要素へ戻します。どの要素もフォーカスを持っていなかったときは、フォーカスはメニューから外れるだけです。

誰がメニューを閉じるかはプラットフォームの流儀に従います。Web と React Native では、閉じるイベントを受けて open(visible)を false に戻します。Compose では、TanreiMenuCloseReason を受け取る onDismissRequest で expanded を false にします。Flutter では、メニューが理由を添えて onClose を呼び、コントローラーを通じて自分で閉じます。SwiftUI では、メニューが isPresented に false を書き込むので、理由は伝わりません。

外側を押したとき、その下にある要素にも押下が届くかどうかも、プラットフォームによって違います。Web と Flutter では届きます。SwiftUI、Compose、React Native では、メニューが閉じるだけです。

推奨

  • ラベルは短くし、アクションは「リンクをコピー」「ファイルを削除」のように、何をするかが分かる言葉にします。
  • アクションのキーボードショートカットは shortcut に中立なキー名(mod+c)で書き、キーはプロダクト側で割り当てます。trailingText はそれ以外のものに使います。末尾のテキストがショートカットとして伝わることはありません。
  • 破壊的なアクションは最後に置き、グループで分けるかグループの後ろに置いて、ほかの項目から離します。
  • ときどき使えるようになる項目は、無効にしたまま同じ場所に残し、メニューの形を変えないようにします。無効な項目にも矢印キーで届きます。

非推奨

  • 画面の主なアクションをメニューに入れない。
  • 互いに排他的な選択にチェックボックス項目を使わない。グループに入れたラジオ項目を使います。
  • 項目が 1 つだけのメニューを作らない。そのアクションはボタンとして見せます。
  • よく使うアクションをサブメニューに入れない。サブメニューを 2 階層より深くしない。
  • アクションの末尾に chevron_right を置かない。それはサブメニュー項目の印です。
  • サイトの主要なナビゲーションを、リンクのメニューで作らない。メニューは求められて開き、消えるものです。

アクセシビリティ

ロール
menu
ラベル付け
The menu is named by its anchor (a context menu by the name the product gives it, and by none where it gives none), and a submenu by its submenu item's label. Each item's label is its name and its supporting text its description; its shortcut is exposed as the item's keyboard shortcut where the platform has one, and otherwise given as text in its description (KBS-10); trailing text is description only. A link item is a menu item like any other, named by its label; while it opens a new context its targetLabel ends its description. Checkbox and radio items expose their checked state, whatever their mark looks like. A submenu item exposes that it opens a menu and whether that menu is open. A group is named by its title. Icons, the mark and the submenu indicator are decorative.
最小ターゲット
見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px

キーボード操作

キー 動作
Enter, Space / Down Arrow on the anchor The product opens the menu; focus moves to the first enabled item.
Shift+F10 / the ContextMenu key on the area / an element inside it (trigger context) The menu asks to be opened against that element (contextRequest); the product opens it, and focus moves to the first enabled item.
Down Arrow / Up Arrow Moves focus to the next or previous item of the level that holds focus, wrapping at the ends.
Home / End Moves focus to the first or last item of that level.
Printable characters Moves focus to the next item of that level whose label begins with the characters typed.
Right Arrow (Left Arrow in a right-to-left layout) on a submenu item Opens the submenu and moves focus to its first enabled item.
Left Arrow (Right Arrow in a right-to-left layout) in a submenu Closes that submenu and returns focus to its submenu item.
Enter Activates the focused item and closes the menu and its submenus, a link item's destination opening after that; on a submenu item, opens the submenu instead.
Space Activates the focused item, a link item as Enter does; a checkbox or radio item leaves the menu open; on a submenu item, opens the submenu.
Enter with a modifier key, on a link item (the web) What the browser does with a link: another tab or window, a download. The menu emits no close and stays open, with focus on the item (MNU-54).
Escape Closes the innermost open submenu and returns focus to its submenu item; with no submenu open, closes the menu and returns focus to the anchor (a context menu: to the element that had focus when it was asked for).
Tab / Shift+Tab Closes the menu and every submenu, and moves focus on from the anchor.

備考

  • The anchor is the product's own button, icon-only Button or field. The menu does not draw it; it reads its position, returns focus to it, and sets its expanded state where the platform has one (aria-haspopup and aria-expanded on the web).
  • A context menu (trigger context) is the same menu opened at a point of an area instead of from a button: a secondary click, a long press, Shift+F10 or the ContextMenu key, as the WAI-ARIA menu pattern names for a context-specific menu. The area is not a button, so it carries no popup state; the menu cancels the platform's own context menu only where it answers the request, and a product that cancels contextRequest — over a field, a link, selected text it does not handle — leaves the platform's menu in place. Every action a context menu offers is also reachable elsewhere in the product, because a context menu is found only by those who know to ask for it (MNU-60 to MNU-63).
  • Five item kinds: item (an action), checkbox-item, radio-item, submenu and group. A checkbox item's checked is its own; which radio item is checked is its group's, by value; a submenu item's open is its own. A group may hold items of any kind except another group; a submenu may hold items of every kind.
  • A link item is the platform's own link exposed as a menu item: on the web an anchor with the menu item role. Assistive technology reads a menu by its items — how many, which one, the arrow keys — and that reading holds only while every child of the menu is a menu item; so the role stays, and what makes the row a link stays underneath it: its address, the browser's context menu and new-tab gestures, a crawler that follows it. It is the one link form that Space activates, because it is operated as a menu item and looks like the action beside it. A menu of nothing but links to a site's main pages, always in view, is navigation and belongs in a navigation landmark of links; the menu is for a short set that is out of the way until asked for (the folded levels of a trail, a choice of language, "more").
  • Focus: the menu is an operated overlay that is not modal. It moves focus in when it opens and returns it to the anchor when it closes, but does not keep it: Tab leaves the menu and closes it, at whatever level focus is. A submenu takes focus when it is opened by a key or a press and returns it to its submenu item when it closes alone.
  • Submenus follow the WAI-ARIA menu pattern: the arrow key that points to where the submenu opens, Enter and Space open it and move focus in; the opposite arrow key and Escape close it and return focus to its item; type-ahead, Home, End and the vertical arrow keys work inside the level that holds focus. A pointer opens a submenu by resting on its item, and may then cross other items on its way to the submenu without closing it (MNU-45); touch opens it with a tap. In a window too narrow to set two surfaces side by side the submenu is laid over its parent, never swapped in for it, so the keys, the roles and the reading order are the same at every size (MNU-47).
  • Depth: one level of submenus where possible, never more than two. Each level costs a person a precise sideways movement and hides the level before it on a small screen; a set that needs more belongs in a dialog, a sheet or a page. A debug build reports a third level; nothing stops it from working (MNU-52).
  • A checked item is told from an unchecked one by its mark, which differs in shape (an empty and a filled box, a thin and a heavy ring, a check or none), never by the row's fill alone: the fill supports the mark and is not held to a contrast of its own. It is still meant to be seen: in the default system a checked row's fill is a layer of the row's content color over the sheet, so it differs from the sheet and from a hovered row by the same step in every scheme (MNU-37), and the content on it keeps at least 4.5:1 in every state. The control looks are drawings in the mark column; the menu holds no checkbox and no radio button.
  • Under gap no separator is exposed between groups; the groups themselves, and their titles, carry the structure to assistive technology, as they do under divider.
  • A menu holds only its items. A field, slider or switch inside a menu would take arrow keys and typing that the menu needs.
  • Text size does not clip an item: items grow at 200% text size, and the surface scrolls when it no longer fits the visible part of the window.
  • An item's shortcut is drawn by the keyboard shortcut component (KBS-13), which writes the keys for the keyboard the person uses and only shows them: the product binds them. Trailing text is never read as a shortcut; a product that typed its shortcut there keeps the text but not the keyboard shortcut semantics until it moves the keys to shortcut.
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.
  • WCAG 2.5.8 (target size): for touch every row is at least the touch target high in both densities (MNU-12). The compact density is lower than the pointer target for a fine pointer, by design: it is the density of a tool used with a mouse, it stays above the 24 by 24 the criterion asks, and its rows reach across the surface's whole width. A product that cannot accept that uses regular.

生成元 specs/menu.spec.json, tokens/sys/measure.tokens.json

コンポーネントが担うこと

  • サーフェスはメニューとして伝えます。アクションはメニュー項目、チェックボックス項目はチェックボックスのメニュー項目、ラジオ項目はラジオのメニュー項目として、それぞれチェック状態、位置、メニュー全体の件数とともに伝えます。
  • 各項目はラベルを名前とし、補足テキストを説明とします。項目の shortcut は、プラットフォームにキーボードショートカットのプロパティがあればそこに(Web では aria-keyshortcuts)、なければキー名を読み上げる形で説明の一部として伝えます。末尾のテキストは、いつも説明の一部としてだけ伝えます。アイコンと印は隠します。箱、輪、チェックは絵であり、チェックの状態は項目自身が持ちます。
  • リンク項目は、リンクではなくメニュー項目として伝えます。メニューは、項目とその数、そして矢印キーによって読まれ、それが成り立つのは、すべての行がメニュー項目であるあいだだけだからです。その下では、プラットフォームのリンクのままです。新しいコンテキストで開くあいだは、targetLabel が説明の最後に加わり、印は隠されます。current のリンク項目は、現在のページとしても伝えます。
  • グループは、タイトルを名前とするグループとして伝えます。区切り線はフォーカスを受けないセパレーターです。グループを余白で分けるときは、セパレーターはありません。タイトルもフォーカスを受けず、項目としては読み上げません。
  • サブメニュー項目は、メニューを開く項目として、展開の状態とともに伝えます。無効な間も同じです。そのサーフェスは、項目のラベルを名前とする独立したメニューで、位置はサーフェスごとに数えます。
  • プラットフォームにそのプロパティがあれば、アンカーにも「メニューを開く」ことと、開いているかどうかを伝えます(Web では aria-haspopup、aria-controls、aria-expanded)。
  • コンテキストメニューは領域に手を加えません。領域はボタンではなく、メニューを開くことも伝えません。メニューは右クリック、長押し、Shift+F10、ContextMenu キー、支援技術のコンテキストメニューのコマンドで開き、名前はプロダクトが付ける aria-label だけです。プラットフォーム自身のコンテキストメニューにも名前はありません。
  • 開くとフォーカスを最初の有効な項目へ移し、閉じるとき、フォーカスがメニューの中にあればアンカーへ戻します。上下の矢印キーは端で折り返し、Home と End で両端へ、文字を打つとラベルがその文字で始まる次の項目へ移ります。Enter で実行して閉じます。チェックボックス項目とラジオ項目を Space で実行したときは開いたままです。Escape で閉じてフォーカスを戻し、Tab で閉じてアンカーから次へ進みます。サブメニューの中では、行の始まりの方を指す矢印キーと Escape がその階層だけを閉じ、フォーカスをその項目へ戻します。行に沿った矢印キーは、何もしないときでも、メニューの背後のページには届きません。
  • 無効な項目にも矢印キーで届きますが、実行はできません。
  • フォーカスリングは項目の内側に描くので、サーフェスに切られません。キーボードフォーカスのときだけ表示します。ポインターを載せた項目は、フォーカスではなくホバーとして描きます。
  • どの項目も高さは最小ターゲット以上で、行全体がヒット領域です。
  • 文字を大きくすると項目は伸び、画面に収まらなくなるとサーフェスがスクロールします。
  • アイコンフォントがまだチェックなどのグリフの印を描けない間は、その場所に何も描かず、「check」という文字を出すこともありません。列の幅は保ち、チェックの状態も伝えます。フォントが読み込まれると、印が現れます。
  • ラベルのない項目、グループの外のラジオ項目、どのラジオ項目も指していないグループの値、同じ値を持つ 2 つのラジオ項目、グループの中のグループ、ショートカットと末尾のテキストの両方を持つ項目、メニューが定義していない separation や iconSpace、3 階層目のサブメニュー、項目のないサブメニューは、デバッグビルドで報告します。新しいコンテキストで開くのに targetLabel のないリンク項目、チェックボックス項目・ラジオ項目・サブメニュー項目に付けた href、スクリプトを指す href や読み取れない href、メニューが定義していない target、href のない項目に付けた current も報告します。メニューが定義していない trigger も報告します。

プラットフォームごとの既知の制約:

  • SwiftUI、Compose、Flutter、React Native にはリンク項目がありません。これらでは、項目はまだ href を受け取りません。
  • SwiftUI、Compose、Flutter、React Native にはコンテキストトリガーがありません。これらのメニューはアンカーからだけ開きます。
  • SwiftUI、Compose、Flutter、React Native には、サブメニュー、separation、iconSpace、markIcon、チェックされた行の塗りがありません。いずれかの項目が印やアイコンを描くメニューでは、すべての行でその列を空けておきます。また、まだチェックを 16 の枠に独自の印で描き、グループのタイトルをラベルの位置から始めます。
  • m3-like と cupertino のタイトルの開始位置は Tanrei の決まりに従うもので、元のデザインと照らし合わせて測ってはいません。
  • Web:メニューは Android の戻るジェスチャーのようなプラットフォームの閉じる要求を受け取らないため、back は送られません。
  • Web:aria-haspopup と aria-expanded はアンカーに設定します。アイコンだけの <tanrei-button> のようなカスタム要素のアンカーでは、シャドウルートの中のボタンに設定します。aria-controls と、アンカーから取るメニューの名前は、シャドウルートの境界を越えられません。
  • Web:サーフェスの位置は、CSS のアンカー配置ではなくスクリプトで決めています。
  • Web:サブメニュー項目は、aria-controls で自分のサーフェスを指せません。aria-controls はシャドウルートの中へ届かないからです。代わりに、サーフェスはアクセシビリティツリーで項目の子孫になります。
  • Web(macOS の WebKit):サブメニューを開いている項目は、押されても押下の色にならず、ホバーの色のままです。
  • SwiftUI には、メニュー、メニュー項目、セパレーターのロールも、ショートカットのプロパティもなく、アンカーがメニューを開くことや、メニューが開いていることを伝える手段もありません。どの項目も位置(「2 of 3」、英語のみ)を値に持つボタンで、チェックボックス項目はトグル、ラジオ項目はチェックされている間は選択済みとして伝わります。
  • SwiftUI は無効なボタンにキーボードフォーカスを渡さないため、矢印キーは無効な項目に止まらず、飛ばして進みます。
  • SwiftUI はメニューをポップオーバーとして表示し、位置はポップオーバー自身が決めます。トークンが決めるのは上下どちらに開くかだけで、アンカーや画面の端からの距離、揃え方、閉じるときの動きはポップオーバーのものです。iOS 26 では、サーフェスをポップオーバー自身のより大きな角丸で切り抜き、独自の影を描きます。実機での見た目はまだ確かめていません。
  • SwiftUI:Tab でメニューは閉じてフォーカスはアンカーへ戻りますが、同じキー操作でそこから次へは進めません。
  • SwiftUI は、ラジオ項目の値を、TanreiMenuOption そのものに付けた .tag(_:) からしか読めません。
  • SwiftUI:矢印キー、Home、End、先頭文字での移動、Escape、Tab は、規則としてテストしているだけで、実際のキーボードではまだ確かめていません。
  • React Native はメニューを TanreiProvider のオーバーレイ層に描くため、上位に TanreiProvider が必要です。Tab でメニューは閉じてフォーカスはアンカーへ戻りますが、同じキー操作でそこから次へは進めません。次の Tab で先へ進みます。
  • React Native:メニューが開いている間に、背後の要素をスクリーンリーダーで操作したときの動きと、フォーカスがメニューの外へ出るときの動きは、実機ではまだ確かめていません。
  • React Native には、チェックボックスやラジオのメニュー項目のロールも、ショートカットのプロパティもありません。どの項目もメニュー項目のロールで、チェックボックス項目とラジオ項目はチェック状態を持ち、末尾のテキストは説明の一部として読み上げられます。
  • React Native では、メニューからアンカーのプロパティを設定できません。accessibilityState={{ expanded: open }} は利用者側で付けてください。
  • React Native では、メニューが開いている間にアンカーが動いても付いていきません。アンカーを測るのは、メニューが開いたときとウィンドウの大きさが変わったときだけです。
  • React Native:矢印キー、先頭文字での移動、Escape、Tab、Space と Enter の違いは、ハードウェアキーボードをつないだ実機ではまだ確かめていません。
  • Compose には、メニュー、メニュー項目、セパレーターのロールも、ショートカットのプロパティもありません。サーフェスはコレクションとして伝わり、アクションにはロールがなく、チェックボックス項目はチェックボックス、ラジオ項目はラジオボタンとして伝わり、末尾のテキストは説明の一部として読み上げられます。アンカーの展開状態もメニューからは設定できません。
  • Compose:メニューが開いている間にアンカーを押すと、外側を押したものとして扱われます。メニューは outside で閉じ、押下はアンカーに届きません。
  • Compose:ポップアップのウィンドウフォーカス、実際の外側への押下、戻るボタン、アンカーへのフォーカスの戻り、そこから Tab で次へ進むこと、メニューでの TalkBack は、Robolectric でしか確かめておらず、実機ではまだです。
  • Flutter には、グループやセパレーターのロールがなく、メニューにアンカーの名前を付けることも、アンカーがメニューを開くことを伝えることもできず、項目に位置と件数もありません。グループは、タイトルを名前とするコンテナとして伝わります。アンカーの展開状態は伝わります。
  • Flutter:TalkBack、VoiceOver、Web のスクリーンリーダーがメニューをどう読み上げるかと、開いたメニューでの Android の戻るジェスチャーは、実機ではまだ確かめていません。

利用者側で行うこと

  • メニューにはアンカーと同じ名前を付けます。アイコンボタンのアンカーには「その他」「並べ替え」のようなラベルを付けます。React Native では、同じ名前をメニューの accessibilityLabel にも渡します。
  • Compose と React Native では、メニューが開いているかどうかをアンカーで伝えます。メニューからは設定できません。
  • メニューに求められたら閉じます。閉じたときに自分でフォーカスを動かす必要はありません。メニューがアンカーへ戻します。
  • コンテキストメニューには「キャンバス」のように aria-label で名前を付け、その各アクションをプロダクトの別の場所にも置きます。
  • アイコンを表示する項目も含め、すべての項目にラベルを付けます。ラベルが名前になり、先頭文字での移動もラベルで照らし合わせます。
  • ラジオ項目のグループには、何についての選択かが分かるタイトルを付けます。
  • すべてのサブメニュー項目にラベルを付けます。ラベルは、項目と、それが開くサブメニューの名前になります。
  • 新しいタブで開くリンク項目には、「新しいタブで開きます」のように、プロダクトの言語で targetLabel を指定します。Tanrei は言葉を同梱しません。