スプリットボタン
主操作ボタンの右端に▼トグルが連結し、押すと別動作(名前を付けて保存など)のメニューが開く。外側クリック・Escで閉じる。
プレビュー
クリック
操作クリック/タップで動きます。
仕様書
- 向く場面
- 保存+名前を付けて保存など主操作と派生操作
- 既定の動作を1クリック、別動作を選べる
- ツールバーの操作をまとめたい
- 避ける場面
- 派生操作が無い(普通のボタンでよい)
- 主操作と派生操作が無関係
- ▼にラベルもメニューの役割も付けない実装
- 構成要素
- 主操作ボタン
- ▼トグル
- メニュー
- メニュー項目
- 結果表示
- 持っている状態
- 通常
- ホバー
- フォーカス
- 押下中
- キーボード
Tab で主ボタン→▼へ・Enter/Space で実行/開閉・▼で↓を押すと開いて先頭へ・↑↓ Home End で移動・Tab は項目内で循環・Esc で閉じて▼へ戻る
- 支援技術
▼に aria-haspopup=menu/aria-expanded/aria-label、メニューは role=menu/menuitem、結果は role=status(APG Menu Button)。主ボタン・▼・項目すべてに focus-visible の輪郭。reduced-motion 対応
- 動き
メニュー opacity と translateY 6px 0.18s ease・▼回転 0.18s。開く方向を示す
デザインの要点
- 主操作と▼は別の button にする。1 つのボタンの右端だけ別動作にすると、キーボードでは派生操作に届かない
- 継ぎ目は inset の box-shadow 1 本だけ。border で引くと角丸の外枠と二重になり、連結して見えない
- ▼で ↓ を押したらメニューを開いて先頭項目へ、Esc で閉じて▼へ戻す。開閉のたびにフォーカスの行き先を決めておく
- 外側クリックで閉じる判定は split.contains(e.target)。メニューだけを対象にすると、▼を押した瞬間に閉じる
実装メモ
プレビューは枠内に収めるためメニューを split ラッパー基準の position:absolute で下方向に開いています。継ぎ目の仕切りは box-shadow の inset で1本だけ描いています。実ページでメニューが画面下端で切れる場合は、開く方向(top / bottom)を配置に応じて出し分けてください。▼トグルは aria-haspopup / aria-expanded を持ち、閉状態で下キーを押すと開いて先頭項目へフォーカスします。
コード(コピーして使えます)
<div class="stage" id="stage">
<div class="split" id="split">
<button class="split-main" id="splitMain">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true">
<path d="M2.5 2.5h7l3 3v7a.5.5 0 01-.5.5h-9a.5.5 0 01-.5-.5v-9a.5.5 0 01.5-.5z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
<path d="M4.5 2.5v3h5v-3M5 13v-3.5h5V13" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
</svg>
保存
</button>
<button class="split-toggle" id="splitToggle" aria-haspopup="menu" aria-expanded="false" aria-label="他の保存方法">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<polyline points="2,4 6,8 10,4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="split-menu" id="splitMenu" role="menu" aria-label="保存の種類" aria-hidden="true">
<button class="split-item" role="menuitem">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<path d="M2.5 2.5h6l3 3v6a.5.5 0 01-.5.5h-8.5a.5.5 0 01-.5-.5v-8.5a.5.5 0 01.5-.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
<path d="M8 9.5l2-2M10 9.5l-2-2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
</svg>
名前を付けて保存
</button>
<button class="split-item" role="menuitem">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<rect x="2.5" y="4.5" width="7" height="7" rx="1.2" stroke="currentColor" stroke-width="1.2"/>
<path d="M5 4.5V3a1 1 0 011-1h5a1 1 0 011 1v5a1 1 0 01-1 1h-1.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
</svg>
コピーを保存
</button>
<button class="split-item" role="menuitem">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<path d="M7 1.8l1.5 3.1 3.4.5-2.45 2.4.58 3.4L7 9.9l-3.03 1.7.58-3.4L2.1 5.4l3.4-.5L7 1.8z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
</svg>
テンプレートとして保存
</button>
</div>
</div>
<p class="split-result" id="splitResult" role="status" aria-live="polite"></p>
</div>