表示例
アイコン: Font Awesome 7 Free(fa-solid / fa-regular)
別名・英語表記
- 分割ボタン
- ドロップダウン付きボタン
- メニュー付きボタン
- 三角付きのボタン
- プルダウン付きボタン
- Split button
実装例
<div class="x-split-button">
<div class="x-split-button__g" role="group" aria-label="保存の操作">
<button class="x-split-button__main" type="button">保存</button>
<button class="x-split-button__more" type="button" aria-label="その他の保存方法" aria-haspopup="menu" aria-expanded="true" aria-controls="x-split-button-m">
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
</div>
<!-- 見本なので開いた状態で流れの中に置いている。実際は ul に popover 属性、▼に popovertarget="x-split-button-m" を付けて開閉し、
aria-expanded は false から始めて toggle イベントで書き換える(hidden 属性では閉じない)。開いたら最初の項目へフォーカスを移す -->
<ul class="x-split-button__menu" id="x-split-button-m" role="menu" aria-label="その他の保存方法">
<li role="none"><button class="x-split-button__item" type="button" role="menuitem" tabindex="-1">下書き保存</button></li>
<li role="none"><button class="x-split-button__item" type="button" role="menuitem" tabindex="-1">テンプレートとして保存</button></li>
</ul>
</div>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-split-button,
.x-split-button *:not([class*="fa-"]),
.x-split-button *:not([class*="fa-"])::before,
.x-split-button *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-split-button {
width: 220px;
max-width: 100%;
}
.x-split-button__g {
display: inline-flex;
}
.x-split-button__main,
.x-split-button__more {
min-height: 44px;
border: 1px solid transparent;
background: #1d4ed8;
color: #fff;
font: inherit;
font-size: 14px;
cursor: pointer;
}
.x-split-button__main {
min-width: 88px;
padding: 0 18px;
border-radius: 8px 0 0 8px;
font-weight: 700;
}
/* ▼は別のボタン。1px の白い仕切りで、押す場所が2つあることを見せる */
.x-split-button__more {
width: 44px;
border-left: 1px solid #fff;
border-radius: 0 8px 8px 0;
font-size: 13px;
}
.x-split-button__main:hover,
.x-split-button__more:hover {
background: #1b45bd;
}
.x-split-button__more[aria-expanded="true"] {
background: #1e3a8a;
}
.x-split-button__main:focus-visible,
.x-split-button__more:focus-visible {
outline: 2px solid #1b2028;
outline-offset: 2px;
position: relative;
}
.x-split-button__menu {
margin: 6px 0 0;
padding: 4px;
list-style: none;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
}
.x-split-button__item {
display: block;
width: 100%;
min-height: 40px;
padding: 0 12px;
border: 0;
border-radius: 6px;
background: transparent;
color: #1b2028;
font: inherit;
font-size: 14px;
text-align: left;
cursor: pointer;
}
.x-split-button__item:hover {
background: #eef2ff;
}
.x-split-button__item:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
AIへの指示文
「スプリットボタン(Split button)」を実装してください。
## これは何か
よく使う操作のボタンの右に、小さな▼のボタンがくっついた部品。▼を押すと「下書き保存」のような別のやり方が出てくる。
## 使いどころ
- いつもは「保存」だが、ときどき「下書き保存」「テンプレートとして保存」も使う、のように主な操作と派生の操作があるとき
- 派生の操作のためにボタンを横に並べると場所を取りすぎるとき
## 実装の要件
- 左の主ボタンと右の▼ボタンを隙間なく並べ、境目に1pxの線を入れる。角丸は外側の角だけに付ける
- メニューは▼の下に出す
- 表示例は開いた状態を流れの中に置いている。実運用ではメニューに popover 属性を付けて▼の popovertarget で開閉し、外側クリックと Esc で閉じる動きはブラウザに任せる(Chrome・Edge は 114 から、Safari は 17 から、Firefox は 125 から。iOS の Safari が外側のタップで閉じるのは 18.3 から)
- 項目を選んだときは hidePopover() で閉じる(項目の click は外側クリックではないので、書かないと開いたまま残る)
- ▼の aria-expanded はメニューの toggle イベントの newState(open / closed)に合わせて true / false に書き換え、外側クリックや Esc で閉じたときも古い値を残さない
- 開いたら最初の項目へフォーカスを移すので、▼の click で先に this.focus() を呼ぶ。Safari と macOS の Firefox はマウスで押したボタンにフォーカスを移さず、そのままだと Esc で閉じたときに▼へ戻らない
- 位置は見本の margin も含めて @supports (position-area: block-end) の中だけに書く。popovertarget で開いたメニューは、押した▼が暗黙のアンカーになる(Chrome・Edge は 133 から、Safari は 26 から、Firefox は 147 から)
- @supports の中では margin: 6px 0 0; inset: auto; width: max-content; position-area: block-end span-inline-start; を指定し、▼の下に右端をそろえて出す
- 同じ @supports の中で position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; を付け、下端では上へ、左端では▼の左端にそろえて右へ回す。width: max-content が無いと、左端で回らずに細く縮む
- position-area に対応しないブラウザ向けに、@supports not (position-area: block-end) の中でメニューに margin: auto; を書き、画面の中央に出す(popover の既定の margin: auto に頼ると、margin を 0 にするリセット CSS に消されて画面の端に張りつく)
- 主な操作と▼の中身が別の種類の操作だと混乱する。▼には主な操作の言い換え・変形だけを入れる
- ドロップダウンメニューはボタン全体が開くだけで、押してすぐ実行される操作を持たない。スプリットボタンは左側を押せばその場で実行される
- ボタングループは並んだボタンが対等で、どれも同じ重さ。スプリットボタンは左が主役で、▼は補助
- 左右の境目が見えないと1つのボタンに見え、▼を押したつもりで保存が走る。仕切りの線と▼の幅(44px目安)で押す場所を分ける
## アクセシビリティの要件
- 左と▼は別々の button にし、別々の名前を付ける(例: 「保存」と「その他の保存方法」)。▼のアイコンは aria-hidden="true"
- ▼には aria-haspopup="menu" と aria-expanded を付け、開閉に合わせて true / false を切り替える
- メニューは role="menu" と role="menuitem" で作り、開いたら最初の項目へフォーカスを移す。上下キーで移動、Esc で閉じて▼へフォーカスを戻す
- 2つを role="group" で包んで aria-label を付けると、同じ操作のまとまりだと伝わる
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<div class="x-split-button">
<div class="x-split-button__g" role="group" aria-label="保存の操作">
<button class="x-split-button__main" type="button">保存</button>
<button class="x-split-button__more" type="button" aria-label="その他の保存方法" aria-haspopup="menu" aria-expanded="true" aria-controls="x-split-button-m">
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
</div>
<!-- 見本なので開いた状態で流れの中に置いている。実際は ul に popover 属性、▼に popovertarget="x-split-button-m" を付けて開閉し、
aria-expanded は false から始めて toggle イベントで書き換える(hidden 属性では閉じない)。開いたら最初の項目へフォーカスを移す -->
<ul class="x-split-button__menu" id="x-split-button-m" role="menu" aria-label="その他の保存方法">
<li role="none"><button class="x-split-button__item" type="button" role="menuitem" tabindex="-1">下書き保存</button></li>
<li role="none"><button class="x-split-button__item" type="button" role="menuitem" tabindex="-1">テンプレートとして保存</button></li>
</ul>
</div>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-split-button,
.x-split-button *:not([class*="fa-"]),
.x-split-button *:not([class*="fa-"])::before,
.x-split-button *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-split-button {
width: 220px;
max-width: 100%;
}
.x-split-button__g {
display: inline-flex;
}
.x-split-button__main,
.x-split-button__more {
min-height: 44px;
border: 1px solid transparent;
background: #1d4ed8;
color: #fff;
font: inherit;
font-size: 14px;
cursor: pointer;
}
.x-split-button__main {
min-width: 88px;
padding: 0 18px;
border-radius: 8px 0 0 8px;
font-weight: 700;
}
/* ▼は別のボタン。1px の白い仕切りで、押す場所が2つあることを見せる */
.x-split-button__more {
width: 44px;
border-left: 1px solid #fff;
border-radius: 0 8px 8px 0;
font-size: 13px;
}
.x-split-button__main:hover,
.x-split-button__more:hover {
background: #1b45bd;
}
.x-split-button__more[aria-expanded="true"] {
background: #1e3a8a;
}
.x-split-button__main:focus-visible,
.x-split-button__more:focus-visible {
outline: 2px solid #1b2028;
outline-offset: 2px;
position: relative;
}
.x-split-button__menu {
margin: 6px 0 0;
padding: 4px;
list-style: none;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
}
.x-split-button__item {
display: block;
width: 100%;
min-height: 40px;
padding: 0 12px;
border: 0;
border-radius: 6px;
background: transparent;
color: #1b2028;
font: inherit;
font-size: 14px;
text-align: left;
cursor: pointer;
}
.x-split-button__item:hover {
background: #eef2ff;
}
.x-split-button__item:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- いつもは「保存」だが、ときどき「下書き保存」「テンプレートとして保存」も使う、のように主な操作と派生の操作があるとき
- 派生の操作のためにボタンを横に並べると場所を取りすぎるとき
注意点
- 主な操作と▼の中身が別の種類の操作だと混乱する。▼には主な操作の言い換え・変形だけを入れる
- ドロップダウンメニューはボタン全体が開くだけで、押してすぐ実行される操作を持たない。スプリットボタンは左側を押せばその場で実行される
- ボタングループは並んだボタンが対等で、どれも同じ重さ。スプリットボタンは左が主役で、▼は補助
- 左右の境目が見えないと1つのボタンに見え、▼を押したつもりで保存が走る。仕切りの線と▼の幅(44px目安)で押す場所を分ける
アクセシビリティの要点
- 左と▼は別々の button にし、別々の名前を付ける(例: 「保存」と「その他の保存方法」)。▼のアイコンは aria-hidden="true"
- ▼には aria-haspopup="menu" と aria-expanded を付け、開閉に合わせて true / false を切り替える
- メニューは role="menu" と role="menuitem" で作り、開いたら最初の項目へフォーカスを移す。上下キーで移動、Esc で閉じて▼へフォーカスを戻す
- 2つを role="group" で包んで aria-label を付けると、同じ操作のまとまりだと伝わる