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

メニュー付きボタン

主なアクションを 1 回の押下で実行し、関連するアクションは、隣につながったもう 1 つのボタンから開くメニューで示します。

content_copyMarkdown で表示
カテゴリ
アクション
トークン
comp.button-with-menu
レベル
合成
構成要素
Menu

概要

Web
tanrei-button-with-menu
schedule
SwiftUI
TanreiButtonWithMenu
radio_button_unchecked
Jetpack Compose
TanreiButtonWithMenu
radio_button_unchecked
Flutter
TanreiButtonWithMenu
radio_button_unchecked
React Native
ButtonWithMenu
radio_button_unchecked

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

スプリットボタンの移り先

このコンポーネントの名前はスプリットボタン(split button)ではありません。「スプリットボタン」は 2 つに分かれたボタンという見た目を表す名前で、つなげたボタングループにも当てはまります。このコンポーネントの決定的な特徴は、2 つ目のボタンがメニューを開くことです。振る舞いの ID には、その名前に由来する SPB が付いています。

以前 現在
スプリットボタン(split-button) メニュー付きボタン(button-with-menu)
comp.split-button.*、--tanrei-comp-split-button-* comp.button-with-menu.*、--tanrei-comp-button-with-menu-*
<tanrei-split-button>、TanreiSplitButton(Web のクラス) <tanrei-button-with-menu>、TanreiButtonWithMenu
SplitButton(React、React Native) ButtonWithMenu
TanreiSplitButton(SwiftUI、Compose、Flutter) TanreiButtonWithMenu

Material 3 では、対応するコンポーネントを split button と呼びます。m3-like のプリセットでは、メニュー付きボタンがそれにあたります。

ネイティブの各プラットフォームの名前はまだ変わっていません

仕様、トークン、Web の要素(<tanrei-button-with-menu>)と React のラッパー(ButtonWithMenu)はメニュー付きボタンという名前で、Web はこの改訂を描きます。SwiftUI、Compose、Flutter、React Native は、以前の版をスプリットボタンという名前で提供しているため、このページのそれらのコードは TanreiSplitButton と SplitButton のままです。各プラットフォームが名前を変えたときに書き換えます。

この改訂では次の点が加わります。いまのところ描くのは Web だけです。

  • 2 つの半分は、区切り線(divider)か隙間(gap)でつながります。デザインシステムがすべてのコンポーネントについて 1 度だけ選び、このコンポーネントだけを別にすることも、separation(auto、divider、gap)で 1 つのインスタンスだけを変えることもできます。既定のシステムは区切り線です。
  • 先頭側の半分は、アイコンのある側のパディングが 1 段狭くなります。丸い両端にはパディングが少し加わります。
  • メニューが開いている間も、末尾側の半分は形を変えません。開いた状態はシェブロンだけで示します。
  • trailingWidth(default、narrow)で、末尾側の半分を細く描けます。シェブロンの両側に小さな余白を取り、丸い外側の端には既定の幅と同じくパディングが少し加わります。

バリアント

メニュー付き:メニュー側の半分を押してください

save

使い方

メニュー付きボタンは、2 つのボタンをつなげて 1 つの形にしたものです。先頭側の半分(主アクション側)が主なアクションを実行し、シェブロンのある末尾側の半分(メニュー側)が、関連するアクションのメニューを開きます。メニューの中身はメニュー自身の項目の種類(アクション、チェックボックス項目、ラジオ項目、グループ)で、各プラットフォームでメニューの項目を渡すときと同じやり方で渡します。

<tanrei-button-with-menu label="保存" menu-label="保存のその他のオプション">
  <span slot="icon">save</span>
  <tanrei-menu-item slot="menu" label="名前を付けて保存…"></tanrei-menu-item>
  <tanrei-menu-item slot="menu" label="コピーを保存"></tanrei-menu-item>
</tanrei-button-with-menu>

<script type="module">
  import '@tanrei/web/components/button-with-menu';
  import '@tanrei/web/components/menu';
  const split = document.querySelector('tanrei-button-with-menu');
  // 要素に届く click は、主アクション側のものだけです。
  split.addEventListener('click', save);
  // メニュー側は開閉を求めるだけです。open を設定するのは利用者側です。
  split.addEventListener('toggle', (e) => (split.open = e.detail.open));
  // close にはメニューが閉じる理由(escape、outside、tab、focus-out、item)が付きます。Web に back はありません。
  split.addEventListener('close', () => (split.open = false));
  // 項目の click はメニュー付きボタンで止まります。項目そのものに登録します。
  split.querySelector('[label="名前を付けて保存…"]').addEventListener('click', saveAs);
</script>

<!-- このインスタンスだけ隙間でつなぎ、メニュー側の半分を細くします。separation を省くとデザインシステムに従います。 -->
<tanrei-button-with-menu variant="tonal" separation="gap" trailing-width="narrow" label="マージ" menu-label="マージのその他のオプション">
  <tanrei-menu-item slot="menu" label="スカッシュしてマージ"></tanrei-menu-item>
  <tanrei-menu-item slot="menu" label="リベースしてマージ"></tanrei-menu-item>
</tanrei-button-with-menu>

<!-- 押したときに加えて、400 ms のホバーでも開きます。閉じたときと開いたときのグリフを指定しています。 -->
<tanrei-button-with-menu label="共有" menu-label="共有のその他のオプション" hover-open hover-delay="400">
  <span slot="chevron">more_vert</span>
  <span slot="chevron-open">close</span>
  <tanrei-menu-item slot="menu" label="リンクをコピー"></tanrei-menu-item>
  <tanrei-menu-item slot="menu" label="メールで送信"></tanrei-menu-item>
</tanrei-button-with-menu>
import { ButtonWithMenu, MenuItem } from '@tanrei/react';

// onClick は主アクション側のものです。MenuItem の onClick は項目の要素に直接登録されるので、
// ButtonWithMenu の中でも呼ばれます。受け取るのはネイティブの MouseEvent です。
<ButtonWithMenu
  label="保存"
  menuLabel="保存のその他のオプション"
  open={open}
  onClick={save}
  onToggle={(e) => setOpen(e.detail.open)}
  onClose={() => setOpen(false)}
>
  <MenuItem slot="menu" label="名前を付けて保存…" onClick={saveAs} />
  <MenuItem slot="menu" label="コピーを保存" onClick={saveCopy} />
</ButtonWithMenu>

// このインスタンスだけ隙間でつなぎ、メニュー側の半分を細くします。
<ButtonWithMenu variant="tonal" separation="gap" trailingWidth="narrow" label="マージ" menuLabel="マージのその他のオプション"
  open={mergeOpen} onClick={merge} onToggle={(e) => setMergeOpen(e.detail.open)} onClose={() => setMergeOpen(false)}>
  <MenuItem slot="menu" label="スカッシュしてマージ" onClick={squash} />
  <MenuItem slot="menu" label="リベースしてマージ" onClick={rebase} />
</ButtonWithMenu>

// ホバーでも開きます。hoverDelay が null ならシステムの待ち時間です。
<ButtonWithMenu label="共有" menuLabel="共有のその他のオプション" hoverOpen hoverDelay={400} open={shareOpen}
  onToggle={(e) => setShareOpen(e.detail.open)} onClose={() => setShareOpen(false)}>
  <span slot="chevron">more_vert</span>
  <span slot="chevron-open">close</span>
  <MenuItem slot="menu" label="リンクをコピー" onClick={copyLink} />
</ButtonWithMenu>
import Tanrei

// Button と同じくタイトルが先です。バインディングには、メニュー側とメニューの両方が書き込みます。
TanreiSplitButton("保存", menuLabel: "保存のその他のオプション", isMenuPresented: $showsSaveOptions, action: save) {
    TanreiMenuItem("名前を付けて保存…", action: saveAs)
    TanreiMenuItem("コピーを保存", action: saveCopy)
}

// ホバーでも開きます(macOS と、ポインターを使う iPadOS)。hoverDelay が nil ならシステムの待ち時間です。
// chevron: と openChevron: には、chevron と chevron-open のスロットのグリフを返すビルダーを渡します。
TanreiSplitButton("共有", menuLabel: "共有のその他のオプション", isMenuPresented: $showsShareOptions,
                  opensOnHover: true, hoverDelay: .milliseconds(400), action: share) {
    TanreiMenuItem("リンクをコピー", action: copyLink)
}
import io.github.chnotchy.tanrei.components.TanreiMenuItem
import io.github.chnotchy.tanrei.components.TanreiSplitButton
import io.github.chnotchy.tanrei.primitives.TanreiText
import io.github.chnotchy.tanrei.tokens.TanreiSplitButtonVariant

// メニューと同じく状態ホイスティングです。menuExpanded は呼び出し側で持ちます。
TanreiSplitButton(
    text = "保存",
    onClick = ::save,
    menuContentDescription = "保存のその他のオプション",
    menuExpanded = expanded,
    onMenuExpandedChange = { expanded = it },
    onMenuDismissRequest = { expanded = false }, // TanreiMenuCloseReason を受け取ります
    variant = TanreiSplitButtonVariant.Tonal,
    icon = { TanreiText("save") },
) {
    TanreiMenuItem(text = "名前を付けて保存…", onClick = ::saveAs)
    TanreiMenuItem(text = "コピーを保存", onClick = ::saveCopy)
}

// マウスとスタイラスのホバーでも開きます。hoverDelayMillis が null ならシステムの待ち時間です。
TanreiSplitButton(
    text = "共有",
    onClick = ::share,
    menuContentDescription = "共有のその他のオプション",
    menuExpanded = shareExpanded,
    onMenuExpandedChange = { shareExpanded = it }, // 押下、キー、ホバーのどれでも
    onMenuDismissRequest = { shareExpanded = false },
    chevron = { TanreiText("more_vert") },
    openChevron = { TanreiText("close") },
    openOnHover = true,
    hoverDelayMillis = 400,
) {
    TanreiMenuItem(text = "リンクをコピー", onClick = ::copyLink)
}
import 'package:tanrei/tanrei.dart';

// メニュー付きボタンがコントローラーを通じて自分でメニューを開閉し、onMenuToggle と onMenuClose で知らせます。
TanreiSplitButton(
  label: '保存',
  menuSemanticLabel: '保存のその他のオプション',
  onPressed: save, // null にすると両方の半分が無効になります
  menuController: saveOptions,
  menuChildren: <Widget>[
    TanreiMenuItem(onPressed: saveAs, child: const Text('名前を付けて保存…')),
    TanreiMenuItem(onPressed: saveCopy, child: const Text('コピーを保存')),
  ],
);

// ホバーでも開きます(タッチでは開きません)。chevron と openChevron にはグリフのウィジェットを渡します。
TanreiSplitButton(
  label: '共有',
  menuSemanticLabel: '共有のその他のオプション',
  onPressed: share,
  openOnHover: true,
  hoverDelay: const Duration(milliseconds: 400), // null ならシステムの待ち時間
  menuChildren: <Widget>[
    TanreiMenuItem(onPressed: copyLink, child: const Text('リンクをコピー')),
  ],
);
import { SplitButton } from '@tanrei/react-native';

// 制御コンポーネントです。onMenuToggle で menuVisible を設定し、理由を受け取る onMenuDismiss で false に戻します。
<SplitButton
  label="保存"
  icon="save"
  menuAccessibilityLabel="保存のその他のオプション"
  menuVisible={open}
  onPress={save}
  onMenuToggle={setOpen}
  onMenuDismiss={() => setOpen(false)}
  menuItems={[
    { label: '名前を付けて保存…', onPress: saveAs },
    { label: 'コピーを保存', onPress: saveCopy },
  ]}
/>

// ポインターのホバーでも開きます。chevron と openChevron はグリフの名前です。
<SplitButton
  label="共有"
  menuAccessibilityLabel="共有のその他のオプション"
  chevron="more_vert"
  openChevron="close"
  hoverOpen
  hoverDelay={400}
  menuVisible={shareOpen}
  onPress={share}
  onMenuToggle={setShareOpen}
  onMenuDismiss={() => setShareOpen(false)}
  menuItems={[{ label: 'リンクをコピー', onPress: copyLink }]}
/>

区切り線でつなぐか、隙間でつなぐか

2 つの半分をつなぐ方法は 2 つあります。区切り線(divider)では、2 つが接し、接する側の角は直角になり、継ぎ目に線が引かれます。隙間(gap)では、2 つが狭い隙間を空けて並び、向かい合う側の角が小さくなります。どちらにするかは、パーツをつなぐすべてのコンポーネントについてデザインシステムが 1 度だけ決めます(既定のシステムは区切り線です)。デザインシステムはこのコンポーネントだけを別にでき、separation(auto、divider、gap)は 1 つのインスタンスだけを変えます。2 つの半分はいつも横に並びます。縦の形はありません。

区切り線と隙間

細いメニュー側の半分

既定では、メニュー側の半分は、ボタンの高さと同じ 1 辺の正方形にシェブロンを収め、丸い外側の端にパディングが少し加わります。trailingWidth を narrow にすると、主アクションに添えた細い取っ手になります。シェブロンの両側に小さな余白を取り、丸い外側の端には既定の幅と同じパディングが少し加わるので、シェブロンは曲線に対して中央に見えます(既定のシステムでは 32 / 36 / 36。同じサイズの narrow のアイコンだけのボタンの幅に、そのパディングを足した幅です)。主アクション側の半分は、どちらの幅でも同じです。

メニュー側の幅:default と narrow(各サイズ)

細いメニュー側の半分は、見た目は最小ターゲットより狭くなりますが、ヒット領域は狭くなりません。ターゲットは外側へ、末尾側の端を越えて届きます。narrow のアイコンだけのボタンの横と同じように、そこに余裕を取ってください。

破壊的なアクション

主なアクションが、元に戻せないかもしれないものを削除したり破棄したりするメニュー付きボタン(削除と、メニューの「すべてのバージョンを削除」)には、destructive を設定します。両方の半分が variant の強調を保ったまま、その中でエラーの色を使います。同じ variant の破壊的なボタンと同じです。Filled はエラーの色で、Tonal はエラーのコンテナの色で塗られます。Elevated と Outlined はラベルとシェブロンをエラーの色で描き、Outlined は輪郭と区切り線もその色にします。無効のときは、同じ variant の無効のメニュー付きボタンと同じ見た目です。

destructive が付くのは 2 つの半分だけで、メニューの項目には付きません。メニューの選択肢は、主なアクションが削除しないものを削除することも、その逆もあるので、削除する項目にはそれぞれ destructive を設定します。

破壊的なメニュー付きボタン(各 variant と、隙間でつないだもの)

仕様

サイズ、アイコン、開いた状態、無効状態

savemore_vertclosemore_vertclose

構成要素

パーツ 説明
1 leading The leading half: a button that performs the main action. It holds the optional icon and the label, and carries its own hover, focus and press.
2 trailing The trailing half: a button that opens and closes the menu of more options. It holds the chevron and carries its own hover, focus and press, at its default width or a narrow one. With hoverOpen on, a fine pointer resting on it opens the menu too.
3 container Each half's shape: its fill, outline, elevation and corners. The halves share one look (the destructive one too, SPB-34) and are joined by a divider or by a narrow gap (separation): round outer ends, and under gap tighter corners where they face each other. The trailing half keeps its shape while the menu is open in the default system.
4 icon · 任意 A glyph before the label in the leading half, drawn in the label color; the padding on its side is a step tighter than without it, as Button's.
5 label The leading half's text and its accessible name.
6 chevron The trailing half's glyph. By default it is the icon set's expand_more, turned half a turn while the menu is open. The chevron slot replaces it, and the chevron-open slot gives the trailing half a different glyph while the menu is open.
7 divider · 任意 Under divider, the line on the seam where the two halves touch. Under gap there is none.
8 menu The Tanrei Menu the trailing half opens, anchored to the whole button with menu. It keeps its own tokens and behaviour.
9 focus-ring Gap-then-ring indicator shown for keyboard focus, around whichever half has it.

プロパティ

プロパティ 型 既定値 説明
variant filled | tonal | elevated | outlined "filled" Emphasis, as Button's variants of the same names. A design system may add values.
destructive boolean false Marks a main action that deletes or discards something a person may not get back (Delete, with Delete all in the menu): both halves keep their variant's emphasis and take the error role's color in it, as a destructive Button of the same variant does (SPB-34). Use it for that kind of action alone, never for emphasis. It adds a color, not a meaning: the label still says what the action does (SPB-35). The menu's items are not marked by it: an item that deletes carries its own destructive.
size small | medium | large "medium" Visual height (32 / 40 / 56), matching Button's heights. The hit area always meets the minimum target. A design system may add values.
separation auto | divider | gap "auto" How the two halves are joined. divider: they touch, and a line is drawn on the seam. gap: they stand a narrow gap apart, with tighter corners where they face each other. Auto takes comp.button-with-menu.container.separation, the design system's answer (divider in the default system). The halves always sit side by side: there is no vertical form.
trailingWidth default | narrow "default" How wide the trailing half is. default holds the chevron in a square of the height (32 / 40 / 56); narrow makes the half a slim handle on the main action: the chevron with a small inset on each side, and the padding at the rounded outer end (32 / 36 / 36), as the default width takes it too. The hit area meets the minimum target at both.
label string "" The leading half's visible text and accessible name ("Save"). Required.
menuLabel string "" The trailing half's accessible name ("More save options"). Required: the chevron alone is not a name.
open boolean false Whether the menu is shown. Where open is a plain value (web, Compose, React Native) the product sets it from toggle and sets it to false from close; where it is a binding or a controller (SwiftUI, Flutter) the button with menu opens and closes the menu through it (SPB-2, SPB-16).
disabled boolean false Prevents interaction with both halves and removes them from the focus order.
hoverOpen boolean false Also opens the menu when a fine pointer rests on the trailing half for hoverDelay, and closes that menu when the pointer leaves the trailing half and the menu (SPB-22 to SPB-25). A press and the keyboard still open it as without. Never applies to touch.
hoverDelay number null How long, in milliseconds, a fine pointer rests on the trailing half before the menu opens on hover. Null takes comp.button-with-menu.motion.hover-open-delay. Ignored unless hoverOpen is true.

スロット

スロット 説明
icon The leading half's icon (a glyph of the system's icon set).
chevron The trailing half's glyph in place of expand_more (one glyph of the system's icon set). Alone, it is turned half a turn while the menu is open, as the default is; with chevron-open it is the closed form only (SPB-21).
chevron-open The trailing half's glyph while the menu is open (one glyph of the system's icon set). When it is filled, the trailing half cross-fades between the closed glyph and this one instead of turning (SPB-21).
menu The menu's content: the menu's own item kinds (item, checkbox-item, radio-item and group, as the menu spec defines them), which the button with menu presents in a Tanrei Menu. Fixtures cannot fill it (SPB-15).

イベント

イベント 説明
click The leading half was activated by pointer, touch, keyboard or assistive technology.
toggle The trailing half asks to open or close the menu: by a press or a key on it (SPB-2), or, with hoverOpen on, by a fine pointer resting on it or leaving it and the menu (SPB-22, SPB-23). Carries the open value it asks for.
close The menu asks to be closed; carries the menu's reason: escape, back, outside, tab, focus-out, item or anchor-hidden (in the platform's casing); anchor-hidden when the button with menu is no longer drawn while its menu is open (MNU-65).

状態

振る舞い

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

ID 要件
SPB-1 WHEN the user activates the leading half by pointer, touch, Enter or Space THE button with menu SHALL emit click exactly once, and SHALL NOT open or close the menu.
SPB-2 WHEN the user activates the trailing half by pointer, touch, Enter or Space THE button with menu SHALL emit toggle exactly once with the opposite of open, and WHEN the user presses Down Arrow on the trailing half while open is false it SHALL emit toggle with true; the menu SHALL then open or close through open: through its binding or controller where open is one (SwiftUI, Flutter), and by the product setting open where open is a plain value (web, Compose, React Native). A menu that is open from hover is the exception: activating the trailing half then keeps it open and emits no toggle (SPB-24).
SPB-3 WHILE disabled is true THE button with menu SHALL NOT emit click, toggle or close, SHALL NOT make either half focusable, SHALL expose the disabled state on both halves and SHALL present no menu, whatever open says; it SHALL NOT open the menu on hover, whatever hoverOpen says.
SPB-4 WHILE a half is hovered, keyboard-focused or pressed THE button with menu SHALL render that half's container, label, icon or chevron, outline, elevation and opacity from the component tokens of its variant in that half's highest-precedence active state, with precedence pressed over focused over hovered, and SHALL keep the other half at its own state (neither half is hovered or pressed while the pointer or the press is on the other); focus gained by pointer or touch SHALL NOT select the focused state.
SPB-5 WHEN a half receives keyboard focus THE button with menu SHALL show the focus ring around that half's own shape, without animation; focus gained by pointer or touch SHALL NOT show it.
SPB-6 WHERE a half is smaller than the minimum target THE button with menu SHALL extend that half's hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers without changing its visual size — above and below, and outward past the button with menu's start edge for the leading half and past its end edge for the trailing half — and toward the other half no further than the middle of the gap between them, or the seam where they touch, so that the two never overlap; a narrow trailing half SHALL reach its target the same way and SHALL NOT be drawn wider for it; a platform that cannot tell the input type SHALL use sys.target.touch.
SPB-7 WHILE disabled is true THE button with menu SHALL render both halves' containers, label, icon, chevron, outline, elevation and opacity from the disabled component tokens of its variant. A disabled button with menu SHALL draw its closed form (the trailing half's closed shape and the closed chevron: the chevron slot's glyph, or expand_more when that slot is empty, unturned), whatever open says.
SPB-8 WHEN variant or size names a value that the active design system does not have THE button with menu SHALL render with the default value's tokens, and WHEN separation or trailingWidth names a value it does not define THE button with menu SHALL render with that prop's default (auto, default); in debug builds it SHALL report the unknown value.
SPB-9 WHEN a half of the button with menu is pressed by pointer, touch, Enter or Space THE half 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.
SPB-10 WHILE pressed THE half SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole control and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; label, icon, chevron, outline, elevation, 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.button-with-menu.motion.feedback-duration with comp.button-with-menu.motion.state-easing, fades over comp.button-with-menu.motion.state-duration with comp.button-with-menu.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
SPB-11 THE half 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.
SPB-12 THE button with menu SHALL draw its two halves in one row, joined as its separation names (SPB-30): the leading half at least the height of its size (comp.button-with-menu.container.height.medium for medium, and so on) and as wide as its content, holding its icon, when it has one, and then its label, comp.button-with-menu.container.gap.medium apart, padded at the start by comp.button-with-menu.leading.padding-inline-start-with-icon.medium when it has an icon and by comp.button-with-menu.leading.padding-inline-start.medium when it has none, at the end by comp.button-with-menu.leading.padding-inline-end.medium, and above and below by at least comp.button-with-menu.container.padding-block.medium; and the trailing half as tall as the leading half, holding the chevron centred between comp.button-with-menu.trailing.padding-inline.medium on each side WHILE trailingWidth is default and between comp.button-with-menu.trailing.padding-inline-narrow.medium on each side WHILE trailingWidth is narrow (and so on for each size). Each half SHALL then take the outer padding of SPB-32. THE button with menu SHALL NOT stack its halves: it has no vertical form.
SPB-13 THE button with menu SHALL round the leading half's start corners and the trailing half's end corners by comp.button-with-menu.container.shape, capped at half that half's height (so that the default system's sys.shape.corner.full draws round ends) and eased by sys.shape.corner-smoothing. WHILE its separation is gap it SHALL round the corners where the halves face each other by comp.button-with-menu.container.inner-shape and, WHILE open is true, the trailing half's by comp.button-with-menu.container.inner-shape-open, each capped and eased the same way; WHILE its separation is divider those corners SHALL be square, open or not. WHILE open is true the chevron SHALL show its open form (SPB-21). WHEN open changes THE button with menu SHALL change the chevron, and the trailing half's inner corners where the two tokens differ, over comp.button-with-menu.motion.open-duration with comp.button-with-menu.motion.open-easing, and at once where motion is reduced. THE button with menu SHALL NOT change the shape of a half for any other reason: where comp.button-with-menu.container.inner-shape-open has the value of comp.button-with-menu.container.inner-shape, as in the default system, the trailing half keeps its shape while the menu is open and the chevron alone shows it. Each half SHALL clip its fill, state layer and ripple to its own shape, and draw its outline and its elevation's ring as SPB-26 says.
SPB-14 THE button with menu SHALL set the label in the text style of its size (comp.button-with-menu.label.typescale.medium, and so on) and the label color of its variant and the leading half's state (comp.button-with-menu.filled.label.color for filled at rest, and so on), SHALL NOT truncate it (it stays on one line while it fits, wraps when the layout gives it less width, and the halves grow in height with it), and SHALL draw the icon and the chevron (expand_more, or a chevron slot's glyph, SPB-21) at the icon size of its size (comp.button-with-menu.icon.size.medium, and so on) through the icon helper, each in the label color of its own half's state, with the family and fill of an unselected icon, sys.icon.weight, and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max.
SPB-15 WHEN open becomes true THE button with menu SHALL present the items of its menu slot as a Tanrei Menu, which then behaves as MNU-1 to MNU-29 say, anchored to the whole button with menu with placement bottom-end (below it with the end edges aligned, flipping as MNU-2 says), with the trailing half as the element focus returns to when the menu closes: an operated overlay that is not modal, as the menu is; a menu that opens from hover SHALL NOT take focus (SPB-22). WHEN open becomes false THE button with menu SHALL remove the menu.
SPB-16 WHEN the menu asks to be closed (MNU-5, MNU-6) THE button with menu SHALL emit close once with the menu's reason and SHALL be closed: 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); WHEN an action item of the menu is activated THE button with menu SHALL let the item emit its click first and then emit close with the reason item. A press on the trailing half while the menu is open is not a press outside: it SHALL emit toggle with false (SPB-2), or keep a menu that is open from hover open (SPB-24), and emit no close.
SPB-17 THE button with menu SHALL expose two buttons in reading order, each its own Tab stop: the leading half named by label, and the trailing half named by menuLabel, exposing that it opens a menu and whether the menu is open where the platform has those properties, however it opened; the icon and the chevron (expand_more or a chevron slot's glyph) SHALL be hidden, and the button with menu SHALL expose no element of its own around them.
SPB-18 THE button with menu SHALL cast the blurred layers of each half's elevation, of its variant and that half's state (comp.button-with-menu.elevated.container.elevation for elevated at rest, and so on), outside that half without clipping them, and draw the elevation's ring inside the half (SPB-26); WHEN the elevation or the colors of a half change between states THE button with menu SHALL animate the change over comp.button-with-menu.motion.state-duration with comp.button-with-menu.motion.state-easing, changing them at once where motion is reduced.
SPB-19 WHEN the layout direction is right-to-left THE button with menu SHALL put the trailing half on the left, mirror the order of the leading half's icon and label and apply its paddings by logical start and end, SHALL present its menu with the end edges aligned at the left (the menu's left edge on the button with menu's left edge, below the trailing half: SPB-15 and MNU-2), SHALL NOT mirror the chevron expand_more or change the way it turns, SHALL leave a glyph of the icon slot or of a chevron slot to the icon helper (SPB-28), and SHALL grow a ripple from the point of contact as in left-to-right.
SPB-20 WHILE label is not empty THE button with menu SHALL expose it as the leading half's accessible name, over a name the product gives it on the element (aria-label or aria-labelledby). WHILE label is empty and the product gives the button with menu a name of its own on the element — aria-label, or aria-labelledby naming an element — THE button with menu SHALL expose that name as the leading half's accessible name. WHILE label is empty and the product gives it none, or menuLabel is empty, THE button with menu SHALL still render, and in debug builds report the half that has no accessible name; WHILE the menu slot holds no item THE button with menu SHALL still emit toggle and draw the trailing half's open form, SHALL present no menu, and in debug builds report the empty menu.
SPB-21 WHILE neither the chevron slot nor the chevron-open slot holds a glyph THE button with menu SHALL draw the icon set's expand_more as the chevron, turned half a turn while open is true; WHILE only the chevron slot holds a glyph THE button with menu SHALL draw that glyph in its place, turned the same way; WHILE the chevron-open slot holds a glyph THE button with menu SHALL draw it unturned while open is true and, while open is false, the chevron slot's glyph (or expand_more when that slot is empty) unturned, and WHEN open changes SHALL cross-fade from one to the other instead of turning. Each slot SHALL hold at most one glyph: the button with menu SHALL draw the first and in debug builds report the rest.
SPB-22 WHERE hoverOpen is true, WHEN a fine pointer (a mouse, a trackpad, or a pen that hovers) enters the trailing half while open is false and stays on it for hoverDelay milliseconds, or for comp.button-with-menu.motion.hover-open-delay when hoverDelay is null, THE button with menu SHALL emit toggle with true once, and WHEN open then becomes true the menu SHALL be open from hover: THE button with menu SHALL present it without moving focus into it (in place of MNU-1's move to the first item), focus staying where it was, and a pointer on an item SHALL render the item hovered without moving focus to it (in place of MNU-8). The pointer leaving the trailing half before the delay has passed, a press on either half, or open becoming true by other means SHALL cancel the wait. Touch, and pointer events a platform synthesises from touch, SHALL never open the menu on hover. WHERE hoverDelay is negative or not a number THE button with menu SHALL wait for comp.button-with-menu.motion.hover-open-delay, and in debug builds report the value.
SPB-23 WHILE the menu is open from hover, WHEN the pointer has been off both the trailing half and the menu's surface for comp.button-with-menu.motion.hover-close-delay THE button with menu SHALL emit toggle with false once and SHALL NOT move focus; a pointer that comes back onto either within that time SHALL keep the menu open, and a press outside both SHALL close it as MNU-5 says. WHILE the menu is open for any other reason (a press, a key, or the product setting open) THE button with menu SHALL NOT close it when the pointer leaves.
SPB-24 WHEN the user activates the trailing half by pointer, touch, Enter, Space or Down Arrow while the menu is open from hover THE button with menu SHALL NOT emit toggle, SHALL keep the menu open and SHALL move focus to its first enabled item as MNU-1 says; from then on the menu SHALL be open as if that activation had opened it: the pointer leaving no longer closes it (SPB-23), MNU-8 applies again, and the next activation of the trailing half emits toggle with false (SPB-2).
SPB-25 WHILE the menu is open from hover, WHEN the user presses Escape or the platform's back, wherever focus is, THE button with menu SHALL emit close once with the reason escape or back, SHALL be closed as SPB-16 says, SHALL NOT move focus, and SHALL keep that key from reaching anything else, so that what hover showed can be dismissed without moving the pointer (WCAG 1.4.13); a later Escape reaches the page as usual.
SPB-26 THE button with menu SHALL draw each half's outline (comp.button-with-menu.outline.width.medium wide at the medium size, and so on, in the outline color of its variant and that half's state) and the ring of its elevation (the layer with no offset, no blur and a positive spread, which the default system gives every level above sys.elevation.level0 and which a platform otherwise draws as a border) inside that half's own edge, the outline over the ring. WHILE its separation is gap neither SHALL reach into the comp.button-with-menu.container.inner-gap between the halves, so that the two halves' edges never meet, however narrow that gap is; WHILE its separation is divider each half SHALL draw neither along the edge where the halves touch, so that the outline and the ring run once around the joined shape and the divider (SPB-31) is the only line on the seam. Only the elevation's blurred layers SHALL fall outside a half (SPB-18).
SPB-27 THE button with 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 button with menu is shown THE button with menu SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus, an open menu placed again by the new direction.
SPB-28 WHERE the layout direction is right-to-left THE button with menu SHALL draw the glyph of its icon slot and a glyph of its chevron or chevron-open slot 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 SPB-19 gives. THE button with 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 chevron it draws itself (expand_more) is not marked and is never mirrored, and a mirrored chevron-slot glyph is turned while open as an unmirrored one is (SPB-21).
SPB-29 THE button with menu SHALL lay out its label 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 button with menu SHALL still place the label and align its lines by the layout direction, not by the text's own.
SPB-30 THE button with menu SHALL join its two halves as its separation names: WHILE separation is auto, as comp.button-with-menu.container.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. Under gap the halves SHALL stand comp.button-with-menu.container.inner-gap apart with nothing drawn between them and the inner corners of SPB-13; under divider the halves SHALL touch, with square inner corners and the divider of SPB-31 on the seam. WHEN separation, or the token it follows, changes THE button with menu SHALL draw the other form at once, without animation, keeping its open state and its focus. A platform that cannot draw a choice SHALL fall back to the first value, divider, and state that in its README.
SPB-31 WHILE its separation is divider THE button with menu SHALL draw on the seam between its halves one line comp.button-with-menu.divider.thickness thick, in the divider color of its variant (comp.button-with-menu.filled.divider.color for filled at rest, and so on), centred on the seam, over the halves' fills and state layers, from comp.button-with-menu.divider.inset below the top of the seam to the same distance above its bottom. The line SHALL take no space of its own, SHALL NOT take a press (a press on it goes to the half under the point), SHALL NOT be exposed to assistive technology, and SHALL keep its resting color while either half is hovered, focused or pressed; WHILE disabled is true it SHALL take the disabled divider color of its variant. A half's state layer, ripple and focus ring SHALL still follow that half's own shape.
SPB-32 THE button with menu SHALL add comp.button-with-menu.container.outer-inset to the leading half's start padding, under either separation and at either trailing width, and to the trailing half's end padding, under either separation and at either trailing width, so that content beside a rounded outer end looks as far from it as from the tighter corner on its other side: the leading half SHALL be wider by that amount with its icon and label moved inward by it, and the trailing half SHALL be wider by that amount with its chevron centred in what is left, half of it nearer the leading half. WHILE trailingWidth is narrow THE trailing half SHALL be as wide as the chevron's box, comp.button-with-menu.trailing.padding-inline-narrow.medium on each side of it (of its own size) and the outer padding, with the chevron centred in what is left: a slim half beside a full end needs the compensation as much as a wide one, its chevron being nearer the curve, so it is one outer padding wider than a narrow icon-only Button of its size (BTN-12) where the two components' tokens agree, as in the default system. In a right-to-left layout the start is the right, as SPB-19 says.
SPB-33 WHERE the component that places the button with menu keeps one Tab stop among its items and makes each half a stop of its own (a toolbar, TBR-25), through the internal channel a Button reads from the component that places it (BTN-10), THE button with menu SHALL let that component say which half, if either, is its Tab stop and move focus to either half, SHALL take the other half out of the Tab order while it does, and SHALL keep each half's own name, role, state, keys and activation. WHILE no component does so THE button with menu SHALL keep both halves in the Tab order, as SPB-17 says. It adds nothing to the public surface: no prop, attribute or styling property says which half is a stop.
SPB-34 WHILE destructive is true THE button with menu SHALL render both halves' containers, label, icon, chevron and outline, and the divider, from the destructive parts of its variant (comp.button-with-menu.filled.destructive.container.color, and so on for each variant), each half in its own state from the destructive parts of that state, and its elevation, shape and opacity as it would without destructive; WHILE destructive is false it SHALL render from the variant's own parts. In every shipped system the destructive parts are those of a destructive Button of the same variant (BTN-40) — filled: the error color with the on-error color on it; tonal: the error container with the on-error-container color on it; elevated and outlined: the variant's own container with the label, the icon and the chevron in the error color, and an outlined half's edge and divider in it too — and disabled they are those of the variant's plain disabled look, so that a disabled destructive button with menu looks like any disabled button with menu of its variant. THE button with menu SHALL NOT pass destructive to the items of its menu: an item is drawn destructive by its own destructive alone (MNU-18), since an option of the menu may delete where the main action does not, and the reverse.
SPB-35 THE button with menu SHALL NOT make color the only sign that its main action is destructive: WHILE destructive is true it SHALL keep both halves' roles, accessible names, states and keyboard behaviour exactly as they are without it, and SHALL expose nothing more to assistive technology on the web, where no role or state says it; WHERE the platform has a destructive role of its own (SwiftUI's ButtonRole.destructive) THE button with menu SHALL give it to the leading half, whose press performs the action, and not to the trailing half, which only opens the menu. Its label says what it destroys.
SPB-36 WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE button with 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 the element that carries its role (the leading half carries them) — an id reference by the elements it names in the host's tree or a tree around it, an error message as part of the description while the button with menu is invalid — except those the button with menu owns (aria-haspopup, aria-expanded, aria-controls, aria-disabled), which it SHALL move and not expose. WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE button with menu SHALL take it off that element. WHEN the product sets an aria- attribute the button with menu owns THE button with menu SHALL report it in debug builds.

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

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

仕様 Web SwiftUI Jetpack Compose Flutter React Native
コンポーネント tanrei-button-with-menu TanreiButtonWithMenu TanreiButtonWithMenu TanreiButtonWithMenu ButtonWithMenu
variant · プロパティ variant variant variant variant variant
destructive · プロパティ destructive role destructive isDestructiveAction destructive
size · プロパティ size size size size size
separation · プロパティ separation separation separation separation separation
trailingWidth · プロパティ trailingWidth trailingWidth trailingWidth trailingWidth trailingWidth
label · プロパティ label title text label label
menuLabel · プロパティ menuLabel menuLabel menuContentDescription menuSemanticLabel menuAccessibilityLabel
open · プロパティ open isMenuPresented menuExpanded menuController menuVisible
disabled · プロパティ disabled disabled enabled onPressed disabled
hoverOpen · プロパティ hoverOpen opensOnHover openOnHover openOnHover hoverOpen
hoverDelay · プロパティ hoverDelay hoverDelay hoverDelayMillis hoverDelay hoverDelay
click · イベント click action onClick onPressed onPress
toggle · イベント toggle isMenuPresented onMenuExpandedChange onMenuToggle onMenuToggle
close · イベント close isMenuPresented onMenuDismissRequest onMenuClose onMenuDismiss
備考 <tanrei-button-with-menu label="Save" menu-label="More save options" hover-open hover-delay="400"> with optional glyph elements in slot="icon", slot="chevron" and slot="chevron-open" (the trailing half draws expand_more through the icon helper when both chevron slots are empty), and the menu's item elements (tanrei-menu-item, tanrei-menu-checkbox-item, tanrei-menu-radio-item, tanrei-menu-group) as children with slot="menu", which it forwards into a tanrei-menu in its shadow root. Only the leading half's click reaches listeners on the element; the trailing half fires toggle (a CustomEvent whose detail.open is the value asked for, not the popover's ToggleEvent, which does not leave the shadow root), for a press, a key or a hover alike, and the menu's close fires close with detail.reason. The product sets open from both. hoverDelay is a number of milliseconds; unset (null) takes the token. Hover opening listens for pointerenter / pointerleave with pointerType mouse or pen, and opens the menu through the menu's internal no-focus channel (SPB-22); Escape for a menu open from hover is a capturing keydown on the document. Each half draws its outline as an inside border and its elevation ring as an inset box-shadow layer, the blurred layers staying outside (SPB-26). The trailing half carries aria-haspopup="menu", aria-expanded and aria-controls, and is the menu's focus-return target. separation (auto, divider, gap) and trailing-width (default, narrow) are attributes; auto reads the name in --tanrei-comp-button-with-menu-container-separation. Under divider the halves touch, the rule is drawn over the seam, and neither half draws a border or an inset ring on its inner edge. destructive is a boolean attribute; it draws both halves from the variant's destructive parts, exposes nothing more (SPB-35), and is not passed to the menu's items. TanreiButtonWithMenu(_ title:, icon:, menuLabel:, isMenuPresented: Binding<Bool>, opensOnHover: Bool = false, hoverDelay: Duration? = nil, action:, chevron:, openChevron:) { TanreiMenuItem … }: the title first, as Button(_:action:); chevron and openChevron are optional glyph builders (the chevron and chevron-open slots); the trailing view builder is the menu's content, presented as TanreiMenu is. The trailing half writes the binding (toggle), and so does the menu when it closes (close); a product that needs the close reason reads the menu's. disabled follows .disabled(_:). Hover uses .onContinuousHover (macOS, and iPadOS with a pointer); a nil hoverDelay takes the token. SwiftUI's Menu(primaryAction:) is not used: neither half can be tokenised. Ships the previous revision today (6 px corners, a fixed turning chevron, no hover opening); the rest is owed, including a check of SPB-26 on this platform. Also owed: the component's current name, separation and trailingWidth, the divider (SPB-31), the outer padding (SPB-32), a trailing half that keeps its shape while open (SPB-13), the tighter padding beside the icon, and the new fixtures; reading comp.button-with-menu.container.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. Not built on this platform yet: role: ButtonRole? = nil, as SwiftUI's own Button(role:) takes it: .destructive is destructive (SPB-34), and the role is given to the leading half alone (SPB-35). TanreiButtonWithMenu(text, onClick, menuContentDescription, menuExpanded, onMenuExpandedChange, onMenuDismissRequest, variant, size, enabled, icon, chevron, openChevron, openOnHover = false, hoverDelayMillis: Long? = null) { TanreiMenuItem(…) }: chevron and openChevron are optional glyph lambdas (the chevron and chevron-open slots); the content lambda is the menu's, shown in the TanreiMenu popup. State hoisting: the caller keeps menuExpanded, sets it from onMenuExpandedChange (a press, a key or a hover) and to false from onMenuDismissRequest, which carries the reason. Hover reads PointerEventType.Enter / Exit for PointerType.Mouse and Stylus only; a null delay takes the token. The trailing half is Role.Button with a content description, and its expanded state as expand / collapse semantics actions. enabled is the inverse flag. Ships the previous revision today (6 px corners, a fixed turning chevron, no hover opening); the rest is owed, including a check of SPB-26 on this platform. Also owed: the component's current name, separation and trailingWidth, the divider (SPB-31), the outer padding (SPB-32), a trailing half that keeps its shape while open (SPB-13), the tighter padding beside the icon, and the new fixtures; reading comp.button-with-menu.container.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. Not built on this platform yet: destructive: Boolean = false (SPB-34); Compose has no destructive role, so it draws and adds no semantics (SPB-35). TanreiButtonWithMenu(label:, icon:, chevron:, openChevron:, menuSemanticLabel:, onPressed:, menuController:, menuChildren: [TanreiMenuItem(…)], onMenuToggle:, onMenuClose:, openOnHover: false, hoverDelay: null, variant:, size:), built with the widgets library: chevron and openChevron are optional glyph widgets (the chevron and chevron-open slots); menuChildren are the menu's items, shown through TanreiMenuAnchor. The controller is a TanreiMenuController (the Tanrei menu's own), passed or created internally; it is open, so the button with menu opens and closes the menu itself; onMenuToggle and onMenuClose report it. Hover uses a MouseRegion and ignores PointerDeviceKind.touch; hoverDelay is a Duration?, null taking the token. A null onPressed disables it. Ships the previous revision today (6 px corners, a fixed turning chevron, no hover opening); the rest is owed, including a check of SPB-26 on this platform. Also owed: the component's current name, separation and trailingWidth, the divider (SPB-31), the outer padding (SPB-32), a trailing half that keeps its shape while open (SPB-13), the tighter padding beside the icon, and the new fixtures; reading comp.button-with-menu.container.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. Not built on this platform yet: isDestructiveAction: false, the widgets library's name for it (SPB-34); it draws and adds no semantics (SPB-35). <ButtonWithMenu label icon chevron openChevron menuAccessibilityLabel menuVisible hoverOpen hoverDelay onPress onMenuToggle onMenuDismiss menuItems={[…]} variant size>: chevron and openChevron are optional glyph names (the chevron and chevron-open slots); menuItems is the Menu's items array (the union of its four kinds), shown by a Menu on the overlay layer that TanreiProvider renders. Controlled: the caller sets menuVisible from onMenuToggle and to false from onMenuDismiss, which carries the reason. Hover uses Pressable's onHoverIn / onHoverOut, which fire only for a pointer (iPadOS, Android with a mouse, web); hoverDelay is in milliseconds, undefined taking the token. The trailing half has the button role with accessibilityState.expanded. Ships the previous revision today (6 px corners, a fixed turning chevron, no hover opening); the rest is owed, including a check of SPB-26 on this platform. Also owed: the component's current name, separation and trailingWidth, the divider (SPB-31), the outer padding (SPB-32), a trailing half that keeps its shape while open (SPB-13), the tighter padding beside the icon, and the new fixtures; reading comp.button-with-menu.container.separation as a typed choice needs the native token emitters to write a component's choice of kind as a typed enumeration, which they do not yet. Not built on this platform yet: destructive (SPB-34); it draws and adds no accessibility props (SPB-35).

トークン

トークン 参照先 解決値(ライト)
--tanrei-comp-button-with-menu-container-gap-large sys.space.space100 8px
--tanrei-comp-button-with-menu-container-gap-medium sys.space.space100 8px
--tanrei-comp-button-with-menu-container-gap-small sys.space.space100 8px
--tanrei-comp-button-with-menu-container-height-large — 56px
--tanrei-comp-button-with-menu-container-height-medium — 40px
--tanrei-comp-button-with-menu-container-height-small — 32px
--tanrei-comp-button-with-menu-container-inner-gap sys.space.space25 2px
--tanrei-comp-button-with-menu-container-inner-shape sys.shape.corner.small 6px
--tanrei-comp-button-with-menu-container-inner-shape-open comp.button-with-menu.container.inner-shape 6px
--tanrei-comp-button-with-menu-container-outer-inset sys.space.space50 4px
--tanrei-comp-button-with-menu-container-padding-block-large — 6px
--tanrei-comp-button-with-menu-container-padding-block-medium — 6px
--tanrei-comp-button-with-menu-container-padding-block-small — 6px
--tanrei-comp-button-with-menu-container-separation sys.behavior.separation 0
--tanrei-comp-button-with-menu-container-shape sys.shape.corner.full 9999px
--tanrei-comp-button-with-menu-disabled-container-shape — 9999px
--tanrei-comp-button-with-menu-disabled-opacity — 1
--tanrei-comp-button-with-menu-divider-inset sys.space.space100 8px
--tanrei-comp-button-with-menu-divider-thickness sys.border.hairline 1px
--tanrei-comp-button-with-menu-elevated-container-color sys.color.surface-container-lowest #ffffff
--tanrei-comp-button-with-menu-elevated-container-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-button-with-menu-elevated-destructive-container-color comp.button.elevated.destructive.container.color #ffffff
--tanrei-comp-button-with-menu-elevated-destructive-divider-color comp.button-with-menu.elevated.divider.color #000000 · 8%
--tanrei-comp-button-with-menu-elevated-destructive-label-color comp.button.elevated.destructive.label.color #cd262f
--tanrei-comp-button-with-menu-elevated-destructive-outline-color comp.button.elevated.destructive.outline.color #000000 · 0%
--tanrei-comp-button-with-menu-elevated-destructive-state-layer-color comp.button-with-menu.elevated.destructive.label.color #cd262f
--tanrei-comp-button-with-menu-elevated-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-button-with-menu-elevated-disabled-container-elevation —
--tanrei-comp-button-with-menu-elevated-disabled-destructive-container-color — #1b1b1b · 12%
--tanrei-comp-button-with-menu-elevated-disabled-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-elevated-disabled-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-elevated-disabled-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-elevated-disabled-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-elevated-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-button-with-menu-elevated-focused-container-color — #e8e8e8
--tanrei-comp-button-with-menu-elevated-focused-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-button-with-menu-elevated-focused-destructive-container-color — #fae9ea
--tanrei-comp-button-with-menu-elevated-focused-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-focused-destructive-label-color — #cd262f
--tanrei-comp-button-with-menu-elevated-focused-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-focused-destructive-state-layer-color — #cd262f
--tanrei-comp-button-with-menu-elevated-focused-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-focused-label-color — #1b1b1b
--tanrei-comp-button-with-menu-elevated-focused-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-focused-state-layer-color — #1b1b1b
--tanrei-comp-button-with-menu-elevated-hovered-container-color — #ededed
--tanrei-comp-button-with-menu-elevated-hovered-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-button-with-menu-elevated-hovered-destructive-container-color — #fbeeee
--tanrei-comp-button-with-menu-elevated-hovered-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-hovered-destructive-label-color — #cd262f
--tanrei-comp-button-with-menu-elevated-hovered-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-hovered-destructive-state-layer-color — #cd262f
--tanrei-comp-button-with-menu-elevated-hovered-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-hovered-label-color — #1b1b1b
--tanrei-comp-button-with-menu-elevated-hovered-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-hovered-state-layer-color — #1b1b1b
--tanrei-comp-button-with-menu-elevated-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-button-with-menu-elevated-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-pressed-container-color — #e8e8e8
--tanrei-comp-button-with-menu-elevated-pressed-container-elevation — 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 2px 0px rgb(0 0 0 / 0.04)
--tanrei-comp-button-with-menu-elevated-pressed-destructive-container-color — #fae9ea
--tanrei-comp-button-with-menu-elevated-pressed-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-pressed-destructive-label-color — #cd262f
--tanrei-comp-button-with-menu-elevated-pressed-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-pressed-destructive-state-layer-color — #cd262f
--tanrei-comp-button-with-menu-elevated-pressed-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-elevated-pressed-label-color — #1b1b1b
--tanrei-comp-button-with-menu-elevated-pressed-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-elevated-pressed-state-layer-color — #1b1b1b
--tanrei-comp-button-with-menu-elevated-state-layer-color comp.button-with-menu.elevated.label.color #1b1b1b
--tanrei-comp-button-with-menu-filled-container-color sys.color.primary #1b1b1b
--tanrei-comp-button-with-menu-filled-container-elevation sys.elevation.level0
--tanrei-comp-button-with-menu-filled-destructive-container-color comp.button.filled.destructive.container.color #cd262f
--tanrei-comp-button-with-menu-filled-destructive-divider-color comp.button-with-menu.filled.divider.color #848484
--tanrei-comp-button-with-menu-filled-destructive-label-color comp.button.filled.destructive.label.color #ffffff
--tanrei-comp-button-with-menu-filled-destructive-outline-color comp.button.filled.destructive.outline.color #000000 · 0%
--tanrei-comp-button-with-menu-filled-destructive-state-layer-color comp.button-with-menu.filled.destructive.label.color #ffffff
--tanrei-comp-button-with-menu-filled-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-button-with-menu-filled-disabled-container-elevation —
--tanrei-comp-button-with-menu-filled-disabled-destructive-container-color — #1b1b1b · 12%
--tanrei-comp-button-with-menu-filled-disabled-destructive-divider-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-filled-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-filled-disabled-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-filled-disabled-divider-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-filled-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-filled-disabled-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-filled-divider-color sys.color.outline #848484
--tanrei-comp-button-with-menu-filled-focused-container-color — #323232
--tanrei-comp-button-with-menu-filled-focused-container-elevation —
--tanrei-comp-button-with-menu-filled-focused-destructive-container-color — #d23c44
--tanrei-comp-button-with-menu-filled-focused-destructive-divider-color — #848484
--tanrei-comp-button-with-menu-filled-focused-destructive-label-color — #ffffff
--tanrei-comp-button-with-menu-filled-focused-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-focused-destructive-state-layer-color — #ffffff
--tanrei-comp-button-with-menu-filled-focused-divider-color — #848484
--tanrei-comp-button-with-menu-filled-focused-label-color — #ffffff
--tanrei-comp-button-with-menu-filled-focused-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-focused-state-layer-color — #ffffff
--tanrei-comp-button-with-menu-filled-hovered-container-color — #2d2d2d
--tanrei-comp-button-with-menu-filled-hovered-container-elevation —
--tanrei-comp-button-with-menu-filled-hovered-destructive-container-color — #d13740
--tanrei-comp-button-with-menu-filled-hovered-destructive-divider-color — #848484
--tanrei-comp-button-with-menu-filled-hovered-destructive-label-color — #ffffff
--tanrei-comp-button-with-menu-filled-hovered-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-hovered-destructive-state-layer-color — #ffffff
--tanrei-comp-button-with-menu-filled-hovered-divider-color — #848484
--tanrei-comp-button-with-menu-filled-hovered-label-color — #ffffff
--tanrei-comp-button-with-menu-filled-hovered-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-hovered-state-layer-color — #ffffff
--tanrei-comp-button-with-menu-filled-label-color sys.color.on-primary #ffffff
--tanrei-comp-button-with-menu-filled-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-pressed-container-color — #323232
--tanrei-comp-button-with-menu-filled-pressed-container-elevation —
--tanrei-comp-button-with-menu-filled-pressed-destructive-container-color — #d23c44
--tanrei-comp-button-with-menu-filled-pressed-destructive-divider-color — #848484
--tanrei-comp-button-with-menu-filled-pressed-destructive-label-color — #ffffff
--tanrei-comp-button-with-menu-filled-pressed-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-pressed-destructive-state-layer-color — #ffffff
--tanrei-comp-button-with-menu-filled-pressed-divider-color — #848484
--tanrei-comp-button-with-menu-filled-pressed-label-color — #ffffff
--tanrei-comp-button-with-menu-filled-pressed-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-filled-pressed-state-layer-color — #ffffff
--tanrei-comp-button-with-menu-filled-state-layer-color comp.button-with-menu.filled.label.color #ffffff
--tanrei-comp-button-with-menu-focused-container-shape — 9999px
--tanrei-comp-button-with-menu-focused-opacity — 1
--tanrei-comp-button-with-menu-hovered-container-shape — 9999px
--tanrei-comp-button-with-menu-hovered-opacity — 1
--tanrei-comp-button-with-menu-icon-size-large sys.icon.medium 24px
--tanrei-comp-button-with-menu-icon-size-medium sys.icon.medium 24px
--tanrei-comp-button-with-menu-icon-size-small sys.icon.small 20px
--tanrei-comp-button-with-menu-leading-padding-inline-end-large sys.space.space300 24px
--tanrei-comp-button-with-menu-leading-padding-inline-end-medium sys.space.space200 16px
--tanrei-comp-button-with-menu-leading-padding-inline-end-small sys.space.space200 16px
--tanrei-comp-button-with-menu-leading-padding-inline-start-with-icon-large sys.space.space200 16px
--tanrei-comp-button-with-menu-leading-padding-inline-start-with-icon-medium sys.space.space150 12px
--tanrei-comp-button-with-menu-leading-padding-inline-start-with-icon-small sys.space.space150 12px
--tanrei-comp-button-with-menu-leading-padding-inline-start-large sys.space.space300 24px
--tanrei-comp-button-with-menu-leading-padding-inline-start-medium sys.space.space200 16px
--tanrei-comp-button-with-menu-leading-padding-inline-start-small sys.space.space200 16px
--tanrei-comp-button-with-menu-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-button-with-menu-motion-hover-close-delay sys.motion.duration.medium2 300ms
--tanrei-comp-button-with-menu-motion-hover-open-delay sys.motion.duration.medium2 300ms
--tanrei-comp-button-with-menu-motion-open-duration sys.motion.role.appear.anchored.enter 300ms
--tanrei-comp-button-with-menu-motion-open-easing sys.motion.easing.spatial cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-button-with-menu-motion-state-duration sys.motion.role.state.default 250ms
--tanrei-comp-button-with-menu-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-button-with-menu-outline-width-large sys.border.hairline 1px
--tanrei-comp-button-with-menu-outline-width-medium sys.border.hairline 1px
--tanrei-comp-button-with-menu-outline-width-small sys.border.hairline 1px
--tanrei-comp-button-with-menu-outlined-container-color — #000000 · 0%
--tanrei-comp-button-with-menu-outlined-container-elevation sys.elevation.level0
--tanrei-comp-button-with-menu-outlined-destructive-container-color comp.button.outlined.destructive.container.color #000000 · 0%
--tanrei-comp-button-with-menu-outlined-destructive-divider-color comp.button-with-menu.outlined.destructive.outline.color #cd262f
--tanrei-comp-button-with-menu-outlined-destructive-label-color comp.button.outlined.destructive.label.color #cd262f
--tanrei-comp-button-with-menu-outlined-destructive-outline-color comp.button.outlined.destructive.outline.color #cd262f
--tanrei-comp-button-with-menu-outlined-destructive-state-layer-color comp.button-with-menu.outlined.destructive.label.color #cd262f
--tanrei-comp-button-with-menu-outlined-disabled-container-color — #000000 · 0%
--tanrei-comp-button-with-menu-outlined-disabled-container-elevation —
--tanrei-comp-button-with-menu-outlined-disabled-destructive-container-color — #000000 · 0%
--tanrei-comp-button-with-menu-outlined-disabled-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-outlined-disabled-destructive-outline-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-outlined-disabled-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-outlined-disabled-outline-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-outlined-divider-color comp.button-with-menu.outlined.outline.color #000000 · 8%
--tanrei-comp-button-with-menu-outlined-focused-container-color — #1b1b1b · 10%
--tanrei-comp-button-with-menu-outlined-focused-container-elevation —
--tanrei-comp-button-with-menu-outlined-focused-destructive-container-color — #cd262f · 10%
--tanrei-comp-button-with-menu-outlined-focused-destructive-divider-color — #cd262f
--tanrei-comp-button-with-menu-outlined-focused-destructive-label-color — #cd262f
--tanrei-comp-button-with-menu-outlined-focused-destructive-outline-color — #cd262f
--tanrei-comp-button-with-menu-outlined-focused-destructive-state-layer-color — #cd262f
--tanrei-comp-button-with-menu-outlined-focused-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-focused-label-color — #1b1b1b
--tanrei-comp-button-with-menu-outlined-focused-outline-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-focused-state-layer-color — #1b1b1b
--tanrei-comp-button-with-menu-outlined-hovered-container-color — #1b1b1b · 8%
--tanrei-comp-button-with-menu-outlined-hovered-container-elevation —
--tanrei-comp-button-with-menu-outlined-hovered-destructive-container-color — #cd262f · 8%
--tanrei-comp-button-with-menu-outlined-hovered-destructive-divider-color — #cd262f
--tanrei-comp-button-with-menu-outlined-hovered-destructive-label-color — #cd262f
--tanrei-comp-button-with-menu-outlined-hovered-destructive-outline-color — #cd262f
--tanrei-comp-button-with-menu-outlined-hovered-destructive-state-layer-color — #cd262f
--tanrei-comp-button-with-menu-outlined-hovered-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-hovered-label-color — #1b1b1b
--tanrei-comp-button-with-menu-outlined-hovered-outline-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-hovered-state-layer-color — #1b1b1b
--tanrei-comp-button-with-menu-outlined-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-button-with-menu-outlined-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-button-with-menu-outlined-pressed-container-color — #1b1b1b · 10%
--tanrei-comp-button-with-menu-outlined-pressed-container-elevation —
--tanrei-comp-button-with-menu-outlined-pressed-destructive-container-color — #cd262f · 10%
--tanrei-comp-button-with-menu-outlined-pressed-destructive-divider-color — #cd262f
--tanrei-comp-button-with-menu-outlined-pressed-destructive-label-color — #cd262f
--tanrei-comp-button-with-menu-outlined-pressed-destructive-outline-color — #cd262f
--tanrei-comp-button-with-menu-outlined-pressed-destructive-state-layer-color — #cd262f
--tanrei-comp-button-with-menu-outlined-pressed-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-pressed-label-color — #1b1b1b
--tanrei-comp-button-with-menu-outlined-pressed-outline-color — #000000 · 8%
--tanrei-comp-button-with-menu-outlined-pressed-state-layer-color — #1b1b1b
--tanrei-comp-button-with-menu-outlined-state-layer-color comp.button-with-menu.outlined.label.color #1b1b1b
--tanrei-comp-button-with-menu-pressed-container-shape — 9999px
--tanrei-comp-button-with-menu-pressed-opacity — 1
--tanrei-comp-button-with-menu-tonal-container-color sys.color.secondary-container #eeeeee
--tanrei-comp-button-with-menu-tonal-container-elevation sys.elevation.level0
--tanrei-comp-button-with-menu-tonal-destructive-container-color comp.button.tonal.destructive.container.color #ffedeb
--tanrei-comp-button-with-menu-tonal-destructive-divider-color comp.button-with-menu.tonal.divider.color #000000 · 8%
--tanrei-comp-button-with-menu-tonal-destructive-label-color comp.button.tonal.destructive.label.color #3a211f
--tanrei-comp-button-with-menu-tonal-destructive-outline-color comp.button.tonal.destructive.outline.color #000000 · 0%
--tanrei-comp-button-with-menu-tonal-destructive-state-layer-color comp.button-with-menu.tonal.destructive.label.color #3a211f
--tanrei-comp-button-with-menu-tonal-disabled-container-color — #1b1b1b · 12%
--tanrei-comp-button-with-menu-tonal-disabled-container-elevation —
--tanrei-comp-button-with-menu-tonal-disabled-destructive-container-color — #1b1b1b · 12%
--tanrei-comp-button-with-menu-tonal-disabled-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-disabled-destructive-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-tonal-disabled-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-disabled-destructive-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-tonal-disabled-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-tonal-disabled-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-disabled-state-layer-color — #1b1b1b · 38%
--tanrei-comp-button-with-menu-tonal-divider-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-button-with-menu-tonal-focused-container-color — #dadada
--tanrei-comp-button-with-menu-tonal-focused-container-elevation —
--tanrei-comp-button-with-menu-tonal-focused-destructive-container-color — #ebd9d7
--tanrei-comp-button-with-menu-tonal-focused-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-focused-destructive-label-color — #3a211f
--tanrei-comp-button-with-menu-tonal-focused-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-focused-destructive-state-layer-color — #3a211f
--tanrei-comp-button-with-menu-tonal-focused-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-focused-label-color — #262626
--tanrei-comp-button-with-menu-tonal-focused-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-focused-state-layer-color — #262626
--tanrei-comp-button-with-menu-tonal-hovered-container-color — #dedede
--tanrei-comp-button-with-menu-tonal-hovered-container-elevation —
--tanrei-comp-button-with-menu-tonal-hovered-destructive-container-color — #efdddb
--tanrei-comp-button-with-menu-tonal-hovered-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-hovered-destructive-label-color — #3a211f
--tanrei-comp-button-with-menu-tonal-hovered-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-hovered-destructive-state-layer-color — #3a211f
--tanrei-comp-button-with-menu-tonal-hovered-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-hovered-label-color — #262626
--tanrei-comp-button-with-menu-tonal-hovered-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-hovered-state-layer-color — #262626
--tanrei-comp-button-with-menu-tonal-label-color sys.color.on-secondary-container #262626
--tanrei-comp-button-with-menu-tonal-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-pressed-container-color — #dadada
--tanrei-comp-button-with-menu-tonal-pressed-container-elevation —
--tanrei-comp-button-with-menu-tonal-pressed-destructive-container-color — #ebd9d7
--tanrei-comp-button-with-menu-tonal-pressed-destructive-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-pressed-destructive-label-color — #3a211f
--tanrei-comp-button-with-menu-tonal-pressed-destructive-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-pressed-destructive-state-layer-color — #3a211f
--tanrei-comp-button-with-menu-tonal-pressed-divider-color — #000000 · 8%
--tanrei-comp-button-with-menu-tonal-pressed-label-color — #262626
--tanrei-comp-button-with-menu-tonal-pressed-outline-color — #000000 · 0%
--tanrei-comp-button-with-menu-tonal-pressed-state-layer-color — #262626
--tanrei-comp-button-with-menu-tonal-state-layer-color comp.button-with-menu.tonal.label.color #262626
--tanrei-comp-button-with-menu-trailing-padding-inline-narrow-large sys.space.space50 4px
--tanrei-comp-button-with-menu-trailing-padding-inline-narrow-medium sys.space.space50 4px
--tanrei-comp-button-with-menu-trailing-padding-inline-narrow-small sys.space.space50 4px
--tanrei-comp-button-with-menu-trailing-padding-inline-large sys.space.space200 16px
--tanrei-comp-button-with-menu-trailing-padding-inline-medium sys.space.space100 8px
--tanrei-comp-button-with-menu-trailing-padding-inline-small sys.space.space75 6px
--tanrei-comp-button-with-menu-label-typescale-large sys.typescale.label 500 14px/1.4286
--tanrei-comp-button-with-menu-label-typescale-medium sys.typescale.label 500 14px/1.4286
--tanrei-comp-button-with-menu-label-typescale-small sys.typescale.label 500 14px/1.4286

フィクスチャ

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

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

ガイドライン

使う場面

いつもの形と、いくつかの変化形があるアクションに使います。「保存」と「名前を付けて保存…」、「マージ」と「スカッシュしてマージ」、「送信」と「送信日時を指定」などです。いつもの形は 1 回押すだけで実行でき、変化形もあと 1 回押せば届きます。

どれが「いつもの形」なのかがはっきりしないなら、メニューを開くボタンかアイコンボタンを使います。変化形がないなら、ふつうのボタンで足ります。独立したアクションを横に並べたいなら、ボタングループを使います。

バリアントの選び方

バリアントの強調の度合いはボタンと同じで、強調に限りがあるのも同じです。強いアクションは 1 つの画面に 1 つにします。

バリアント 強調 用途
Filled(既定) 最も強い 画面の主アクション。
Tonal 強い 重要だが、Filled ボタンと競合させたくないアクション。
Elevated 中 込み入った背景や模様のある背景の上に置くメニュー付きボタン。
Outlined 中 Filled ボタンの隣に置く副次的なアクション。

Text バリアントはありません。塗りも枠線もないと 2 つの半分の境目が見えず、分かれていることが伝わらなくなるからです。

サイズはボタンと同じ高さなので、同じサイズのボタンと 1 行に並べられます。既定のシステムでは、外側の端は完全に丸くなります。2 つの半分が接するところは、区切り方によって変わります。区切り線のときは、直角の角で接して継ぎ目に線が引かれ、どちらの半分も継ぎ目に沿っては輪郭を描きません。そのため、Outlined のメニュー付きボタンの輪郭は、ぐるりと 1 本の線になります。隙間のときは、輪郭と、エレベーションのあるサーフェスが持つリングを、それぞれの半分が自分の端の内側に描くので、2 つの半分が触れ合うことはありません。

シェブロン

メニュー側の半分は、アイコンセットの expand_more を描き、メニューが開いている間は半回転させます。既定のシステムでは、メニュー側の半分は開いている間も形を変えないので、開いた状態を示すのはシェブロンだけです。デザインシステムは、開いた半分の内側の角を別に決められます。m3-like のプリセットでは、開いている間は円になります。

2 つのスロットでグリフを変えられます。どちらにも、アイコンセットのグリフを 1 つだけ入れます。

入れたスロット 閉じているとき 開いているとき
なし expand_more expand_more を回転
chevron だけ 指定したグリフ 指定したグリフを回転
chevron-open(chevron はあってもなくても可) chevron のグリフか expand_more chevron-open のグリフ。回転せず、2 つがクロスフェードします

more_vert のように向きのないグリフは、回転させないようにします。chevron-open のグリフと組み合わせるか、両方のスロットに同じグリフを入れます。

ホバーで開く

hoverOpen を指定すると、マウス、トラックパッド、ホバーできるペンがメニュー側に hoverDelay ミリ秒とどまったときにもメニューが開きます。hoverDelay を設定しなければシステムの待ち時間になり、既定のシステムでは 300 ms です。押下とキーボードではこれまでどおり開きます。タッチでは、ホバーで開くことはありません。

  • ホバーで開いたメニューはフォーカスを受け取りません。フォーカスは、たとえばテキストフィールドのように、利用者がいた場所に残ります。項目はホバーを表示し、クリックすれば実行されます。
  • ポインターがメニュー側とメニューの両方から離れて少し経つと閉じます。この猶予があるので、ポインターはメニューへ移ったり、端から少しはみ出して戻ったりできます。ほかの方法で開いたメニューは、ポインターが離れても閉じません。
  • ホバーで開いたメニューが出ている間にメニュー側を押すかキーを押すと、メニューは開いたままフォーカスが中に移ります。以降は押して開いたメニューと同じで、次に押すと閉じます。
  • ホバーで開いたメニューは、フォーカスがどこにあっても Escape で閉じます。
  • ホバーも押下と同じトグルのイベントで開くよう求めるので、open を設定するコードを変える必要はありません。

ホバーで開くのは、ポインターを使うことが多く、メニューがひとりでに現れても邪魔にならない場所(デスクトップのエディターのツールバーなど)に向いています。押して開く操作の代わりにはなりません。

メニュー

  • メニューは Tanrei のメニューそのもので、メニューの機能をすべて備えています。開くとフォーカスが中に移り、矢印キーと先頭文字で項目の間を移動でき、画面からはみ出さないように反対側へ回ったりずれたりします。開く位置はメニュー付きボタン全体の下で、末尾側の端をそろえます。
  • メニューが開いている間は、シェブロンが開いたときの形になります(シェブロンを参照)。
  • メニューが閉じたとき、フォーカスがメニューの中にあれば、メニューを開いたボタンであるメニュー側の半分へ戻ります。
  • メニューが開いているときにメニュー側を押すのは、外側の押下ではなくトグルです。toggle で閉じるよう求め、close は出しません。アクション項目でメニューが閉じるときは、先に項目のクリックが届き、その後に理由 item の付いた close が届きます。
  • 誰がメニューを閉じるかはプラットフォームの流儀に従います。Web、Compose、React Native では、トグルのイベントを受けて open(menuExpanded、menuVisible)を設定し、閉じるイベントを受けて false に戻します。SwiftUI と Flutter では、メニュー付きボタンがバインディングかコントローラーを通じて自分で開閉します。

推奨

  • 主アクション側には、いつもの形のアクションを動詞で書きます。「保存」「マージ」「送信」などです。
  • メニュー側には、メニューに何があるかが分かるラベルを付けます。「その他」や「メニュー」ではなく「保存のその他のオプション」です。
  • メニューには、主アクションの変化形を入れます。主アクションそのものを繰り返す必要はありません。
  • メニューは短く保ちます。数個の項目がちょうどよく、項目が長く並ぶなら、それは 1 つのアクションの変化形ではないのかもしれません。
  • separation は、1 つのインスタンスだけを変える必要がない限り auto のままにします。プロダクトの中の、つなげたコントロールの区切り方がそろいます。
  • 表の行や密度の高いヘッダーのように、メニューが副次的で幅に余裕がないところでは、細いメニュー側の半分を使います。

非推奨

  • 「設定」や「ヘルプ」のような関係のないアクションをメニューに入れない。それらには、専用のメニューを持つアイコンボタンを使います。
  • メニューの項目が 1 つしかないメニュー付きボタンを使わない。両方のアクションをボタンとして並べます。
  • 片方の半分だけを無効にしようとしない。disabled は両方に効きます。変化形が使えないなら、そのメニュー項目を無効にします。
  • メニュー側の半分に、メニューを開く以外の役割を持たせない。メニュー側は常に「メニューを開くボタン」として伝わります。

アクセシビリティ

ロール
button
ラベル付け
The leading half is named by label, the trailing half by menuLabel, which should say what more the menu offers ("More save options"). The trailing half exposes that it opens a menu and whether it is open (aria-haspopup and aria-expanded on the web), however it opened. The icon and the chevron, whether the default or a slot's glyph, are decorative.
最小ターゲット
見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px

キーボード操作

キー 動作
Tab / Shift+Tab Moves focus to the leading half, then to the trailing half.
Enter / Space on the leading half Performs the main action (click).
Enter, Space / Down Arrow on the trailing half Opens the menu (Enter and Space also close it); focus moves to the first enabled item. On a menu that opened on hover, keeps it open and moves focus to its first enabled item.
Escape (in the menu) Closes the menu and returns focus to the trailing half.
Escape (a menu that opened on hover) Closes the menu wherever focus is, and leaves focus where it was.

備考

  • Two buttons, two Tab stops: the trailing half is a menu button in the WAI-ARIA sense, and the menu is the Tanrei Menu with its own keyboard (MNU-7).
  • The menu's items are the menu's own item kinds; their events (an item's click, a checkbox item's change, a group's change) are the menu's, and reach the product as they do from a menu anywhere.
  • Opening on hover is an addition for fine pointers, never a replacement: every menu that hover opens can also be opened by a press and by the keyboard, and keyboard and screen reader use is unchanged. It meets WCAG 1.4.13: the pointer can move onto the menu (the close delay), Escape dismisses it without moving the pointer, and it stays until the pointer leaves or the person dismisses it. It does not take focus, so a person typing elsewhere is not interrupted.
  • Text must remain readable at 200% text size: both halves grow in height together and the label never truncates.
  • 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.
  • The divider is decoration: it is not exposed and takes no press. The two halves are two buttons whether a line or a gap is between them.
  • A narrow trailing half is drawn narrower than the minimum target; its hit area is not (SPB-6). Its target reaches outward past the end edge, so leave room for it there.
  • In the default system the outline of the outlined variant, and the divider of the outlined, tonal and elevated variants, are the hairline (the divider's color), far below 3:1 against their ground. That is within WCAG 1.4.11: the leading half is identified by its label, the trailing half by its chevron, and each half answers hover, focus and press on its own shape. The divider of the filled variant keeps the functional outline role, because a hairline cannot be seen on that fill.
  • destructive adds a color, never a meaning (WCAG 1.4.1, use of color): the error color tells people who see it that the main action deletes or discards something, and nothing tells a screen reader user so but the label. So the label names the action and what it acts on ("Delete project", not "OK"). Disabled, a destructive button with menu looks like any disabled one of its variant: a button that cannot be pressed does nothing.

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

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

  • メニュー付きボタンは、読み上げの順に並んだ 2 つのボタンで、それぞれに Tab で止まります。主アクション側はラベルを、メニュー側はメニューラベルを名前とします。メニュー側は、プラットフォームにそのプロパティがあれば、メニューを開くことと、メニューが開いているかどうかも伝えます。2 つを包む要素はないので、スクリーンリーダーの利用者に聞こえるのは 2 つのボタンだけです。
  • アイコンとシェブロンは、chevron や chevron-open のスロットに入れたグリフも含めて、支援技術から隠します。メニュー側は、押下、キー、ホバーのどれで開いたかにかかわらず、メニューが開いているかどうかを伝えます。
  • 主アクション側では、Enter と Space で主アクションを実行します。メニュー側では、Enter、Space、下矢印でメニューを開き、最初の有効な項目へフォーカスを移します。その後はメニューがふだんどおりに動き、Escape で閉じてメニュー側へフォーカスを戻します。
  • ホバーで開いたメニューはフォーカスを元の場所に残し、フォーカスがどこにあっても、ポインターを動かさずに Escape で閉じられます。ホバーで現れたものは、いつでも消せます(WCAG 1.4.13)。この Escape はほかへは届かず、次の Escape はふだんどおりページに届きます。
  • ホバー、押下、フォーカスには、それぞれの半分が自分で応えます。もう一方の半分が、自分のものでない状態を表示することはありません。
  • それぞれの半分のヒット領域は、外側と上下に最小ターゲットまで広がります。もう一方の半分に向かっては、継ぎ目まで、または隙間の中央までで止まるので、2 つが重なることはありません。細いメニュー側の半分は、主アクション側に向かって届かない分を外側へ伸ばすので、大きく描かれなくてもターゲット全体を持ちます。
  • 区切り線は装飾です。支援技術からは隠され、押しても何も起こりません。間にあるのが線でも隙間でも、2 つの半分は 2 つのボタンです。
  • 既定のシステムでは、Outlined の輪郭と、Outlined・Tonal・Elevated の区切り線は細い線で、あえて控えめにしてあります。主アクション側はラベルで、メニュー側はシェブロンで見分けられます。Filled の区切り線はもっと強い線です。その塗りの上では細い線が見えないからです。
  • ラベルが途中で切れることはありません。文字サイズを大きくすると折り返し、両方の半分がそろって縦に伸びます。
  • destructive が加えるのは色だけです。Web には破壊的なアクションを表すロールも状態もないので、両方の半分のロール、名前、状態、キー操作は変わりません。破壊的なロールがある SwiftUI では、主アクション側の半分がそのロールを持ちます。
  • 無効なメニュー付きボタンは、両方の半分を無効として伝え、フォーカスの順序から外し、メニューを表示しません。open がどうであっても、閉じた形を描きます。
  • ラベルやメニューラベルがない場合と、メニューに項目がない場合は、デバッグビルドで報告します。

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

  • Web:メニュー付きボタンに登録したリスナーに届く click は、主アクション側のものだけです。メニュー項目の click はメニュー付きボタンで止まるので、祖先に登録したリスナーには届きません。項目そのものに登録してください。React のメニュー項目のラッパーが onClick を要素に直接登録しているのは、このためです。
  • SwiftUI には、ボタンがメニューを開くことを伝えるプロパティも、開いているかどうかの状態もありません。メニューのポップオーバーはメニュー側に付けています。2 つの半分は同じ高さで、メニュー側が行の末尾にあるので、その下端はメニュー付きボタン全体の下端と一致します。バインディングがトグルと閉じる要求の両方を兼ねるので、閉じた理由は受け取れません。メニューのスロットが一瞬でも空になると(後から読み込む項目の ForEach など)、デバッグビルドではアサーションの失敗になり、デバッグ中のアプリが止まります。2 つの半分での Enter、Space、下矢印、メニュー側へのフォーカスの戻り、macOS でメニューが開いているときのメニュー側のクリックは、エンドツーエンドではテストしていません。
  • Flutter には、ボタンがメニューを開くことを伝えるプロパティがありません。メニュー側はメニューが開いているかどうかは伝えますが、メニューを開くボタンであることは伝えません。
  • Compose には、ボタンがメニューを開くことを伝えるプロパティがありません。メニュー側は、開閉の状態を展開・折りたたみのアクションとして持つボタンになります。メニュー自体には、メニューのページに挙げた Compose の制約がそのまま当てはまります。
  • React Native にも、ボタンがメニューを開くことを伝えるプロパティがありません。メニュー側はメニューが開いているかどうかは伝えますが、メニューを開くボタンであることは伝えません。
  • React Native:メニューが開いているときのメニュー側の押下、メニュー側へのフォーカスの戻り、シェブロンの回転は、実機なしのテストでしか確かめていません。

利用者側で行うこと

  • 両方のラベルを書きます。シェブロンだけでは名前になりませんし、「保存」の隣の「その他」ではメニューに何があるか分かりません。
  • 破壊的なメニュー付きボタンでは、何を消すのかをラベルで伝えます(「OK」ではなく「プロジェクトを削除」)。エラーの色は、それが見える人にしか伝わりません(WCAG 1.4.1、色の使用)。
  • Web、Compose、React Native では、トグルのイベントを受けて open を設定し、閉じるイベントを受けて false に戻します。メニュー付きボタンは求めるだけです。hoverOpen のときは、ホバーも同じトグルのイベントで求めます。
  • メニューが閉じたとき、自分でフォーカスを動かさないでください。メニュー付きボタンがメニュー側へ戻します。
  • アイコンを表示する項目も含めて、すべてのメニュー項目にラベルを付けます。
  • 細いメニュー側の半分の、末尾側の端の先に余裕を取り、ターゲットが隣のコントロールと重ならないようにします。