表示例
フォルダ
- 受信箱
- 送信済み
- 下書き
打ち合わせの日程
来週の火曜か水曜の午後でご都合はいかがでしょうか。
アイコン: Font Awesome 7 Free(fa-solid / fa-regular)
別名・英語表記
- スプリッター
- リサイズ可能パネル
- ペイン分割
- ウィンドウスプリッター
- ドラッグで幅を変えるパネル
- 仕切りバー
- Splitter
- Split pane
実装例
<div class="x-split-pane">
<div class="x-split-pane__a" id="x-split-pane-a">
<div class="x-split-pane__head">
<p class="x-split-pane__h" id="x-split-pane-h">フォルダ</p>
<!-- ドラッグできない人のために、1回押すだけで畳める手段も置く -->
<button class="x-split-pane__fold" type="button" aria-label="フォルダを畳む"><i class="fa-solid fa-angles-left" aria-hidden="true"></i></button>
</div>
<ul class="x-split-pane__list">
<li><i class="fa-solid fa-inbox" aria-hidden="true"></i>受信箱</li>
<li><i class="fa-regular fa-paper-plane" aria-hidden="true"></i>送信済み</li>
<li><i class="fa-regular fa-file-lines" aria-hidden="true"></i>下書き</li>
</ul>
</div>
<!-- ドラッグと矢印キーで左の幅を変える。値は左のパネル(主パネル)の幅の割合 -->
<div class="x-split-pane__sep" role="separator" tabindex="0" aria-orientation="vertical"
aria-valuenow="40" aria-valuemin="20" aria-valuemax="70" aria-valuetext="40%"
aria-controls="x-split-pane-a" aria-labelledby="x-split-pane-h">
<span class="x-split-pane__grip" aria-hidden="true"></span>
</div>
<div class="x-split-pane__b">
<p class="x-split-pane__h">打ち合わせの日程</p>
<p class="x-split-pane__txt">来週の火曜か水曜の午後でご都合はいかがでしょうか。</p>
</div>
</div>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-split-pane,
.x-split-pane *:not([class*="fa-"]),
.x-split-pane *:not([class*="fa-"])::before,
.x-split-pane *: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;
}
/* 左の幅は変数で持ち、ドラッグやキー操作で JavaScript が書き換える */
.x-split-pane {
--x-split-pane-pos: 40%;
display: grid;
grid-template-columns: var(--x-split-pane-pos) 12px 1fr;
width: 340px;
max-width: 100%; /* 狭い画面では親の幅に合わせて縮む */
height: 170px;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
color: #1b2028;
font-size: 12px;
overflow: hidden;
}
.x-split-pane__a,
.x-split-pane__b {
min-width: 0;
padding: 10px 12px;
overflow: auto;
}
.x-split-pane__a {
background: #f3f4f6;
}
.x-split-pane__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
margin: -4px 0 4px;
}
.x-split-pane__fold {
display: grid;
place-items: center;
width: 24px;
height: 24px;
border: 1px solid #7f8a99;
border-radius: 5px;
background: #fff;
color: #3a4250;
font-size: 11px;
cursor: pointer;
}
.x-split-pane__fold:hover {
background: #e3e8ef;
}
.x-split-pane__fold:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: 1px;
}
.x-split-pane__h {
margin: 0 0 8px;
font-size: 12px;
font-weight: 700;
}
.x-split-pane__head .x-split-pane__h {
margin: 0;
}
.x-split-pane__list {
margin: 0;
padding: 0;
list-style: none;
}
.x-split-pane__list li {
display: flex;
align-items: center;
gap: 7px;
padding: 5px 0;
white-space: nowrap;
}
.x-split-pane__list .fa-solid,
.x-split-pane__list .fa-regular {
width: 14px;
color: #5b6472;
}
.x-split-pane__txt {
margin: 0;
color: #3a4250;
line-height: 1.6;
}
/* 仕切り。見える線は1本だが、つかめる幅は12px取る */
.x-split-pane__sep {
position: relative;
display: grid;
place-items: center;
border-left: 1px solid #7f8a99;
border-right: 1px solid #e3e6ea;
background: #fff;
cursor: col-resize;
touch-action: none;
}
/* つかめる範囲を左右に6pxずつ広げ、24px にする(見た目の幅は変えない) */
.x-split-pane__sep::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: -6px;
right: -6px;
z-index: 1;
}
.x-split-pane__grip {
width: 4px;
height: 28px;
border-radius: 2px;
background: #7f8a99;
}
.x-split-pane__sep:hover .x-split-pane__grip,
.x-split-pane__sep:focus-visible .x-split-pane__grip {
background: #1d4ed8;
}
.x-split-pane__sep:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
AIへの指示文
「分割パネル(Split pane)」を実装してください。
## これは何か
画面を左右(または上下)の2枚のパネルに分け、間の仕切りをつかんで動かすと両側の幅が変わる配置。メールアプリやコードエディターで見かける。
## 使いどころ
- 一覧と詳細、コードとプレビューのように、2つの面を並べて見比べながら作業させたいとき
- どちらの面を広く取りたいかが人や作業によって違い、利用者に幅を決めさせたいとき
## 実装の要件
- display: grid; grid-template-columns: var(--pos) 12px 1fr; にし、JavaScript は変数 --pos だけを書き換える
- ドラッグは pointerdown で setPointerCapture を呼び、pointermove で位置を計算する。仕切りに touch-action: none を付けて、指で動かしたときにページがスクロールしないようにする
- 値を変えるたびに aria-valuenow(必要なら aria-valuetext)も書き換える
- 両側のパネルに min-width: 0 と overflow: auto を付け、狭めたときに中身がはみ出さないようにする
- 幅が固定の左右分割なら 2カラム(layout/two-column)で足りる。仕切りを動かして幅を変えられることが、この部品の要件
- 最小と最大の幅を決める。0 まで縮められると、パネルがあったことに気づけなくなる
- 決めた幅は localStorage などに覚えておく。開くたびに戻ると毎回やり直しになる
- スマートフォンでは左右に並べる幅が無い。狭い画面では縦に積むかタブで切り替え、仕切りは出さない
- ドラッグ中は本文の文字が選択されないよう、つかんでいる間だけ user-select: none にする
## アクセシビリティの要件
- 仕切りは WAI-ARIA APG の Window Splitter の形にする。role="separator" と tabindex="0" でフォーカスできる仕切りにし、aria-valuenow / aria-valuemin / aria-valuemax で主パネルの大きさを数で持つ
- 左右に分ける縦の仕切りには aria-orientation="vertical" を付ける(separator の既定は horizontal)。aria-controls で主パネルを指し、主パネルの名前で aria-labelledby を付ける
- 縦の仕切りは左右の矢印キー、横の仕切りは上下の矢印キーで動かす。Enter で主パネルを畳む/戻す、Home / End で最小/最大へ動かす操作も APG に載っている
- ドラッグだけに頼らない(WCAG 2.2 の 2.5.7 ドラッグ動作)。パネルを畳むボタンや「広げる/狭める」ボタンなど、1回押すだけで幅を変える手段も用意する
- 見える線が細くても、つかめる幅は 24px 相当を確保するか、周りに押せるものを置かない(WCAG 2.2 の 2.5.8 ターゲットのサイズ(最低限))
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<div class="x-split-pane">
<div class="x-split-pane__a" id="x-split-pane-a">
<div class="x-split-pane__head">
<p class="x-split-pane__h" id="x-split-pane-h">フォルダ</p>
<!-- ドラッグできない人のために、1回押すだけで畳める手段も置く -->
<button class="x-split-pane__fold" type="button" aria-label="フォルダを畳む"><i class="fa-solid fa-angles-left" aria-hidden="true"></i></button>
</div>
<ul class="x-split-pane__list">
<li><i class="fa-solid fa-inbox" aria-hidden="true"></i>受信箱</li>
<li><i class="fa-regular fa-paper-plane" aria-hidden="true"></i>送信済み</li>
<li><i class="fa-regular fa-file-lines" aria-hidden="true"></i>下書き</li>
</ul>
</div>
<!-- ドラッグと矢印キーで左の幅を変える。値は左のパネル(主パネル)の幅の割合 -->
<div class="x-split-pane__sep" role="separator" tabindex="0" aria-orientation="vertical"
aria-valuenow="40" aria-valuemin="20" aria-valuemax="70" aria-valuetext="40%"
aria-controls="x-split-pane-a" aria-labelledby="x-split-pane-h">
<span class="x-split-pane__grip" aria-hidden="true"></span>
</div>
<div class="x-split-pane__b">
<p class="x-split-pane__h">打ち合わせの日程</p>
<p class="x-split-pane__txt">来週の火曜か水曜の午後でご都合はいかがでしょうか。</p>
</div>
</div>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-split-pane,
.x-split-pane *:not([class*="fa-"]),
.x-split-pane *:not([class*="fa-"])::before,
.x-split-pane *: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;
}
/* 左の幅は変数で持ち、ドラッグやキー操作で JavaScript が書き換える */
.x-split-pane {
--x-split-pane-pos: 40%;
display: grid;
grid-template-columns: var(--x-split-pane-pos) 12px 1fr;
width: 340px;
max-width: 100%; /* 狭い画面では親の幅に合わせて縮む */
height: 170px;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
color: #1b2028;
font-size: 12px;
overflow: hidden;
}
.x-split-pane__a,
.x-split-pane__b {
min-width: 0;
padding: 10px 12px;
overflow: auto;
}
.x-split-pane__a {
background: #f3f4f6;
}
.x-split-pane__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
margin: -4px 0 4px;
}
.x-split-pane__fold {
display: grid;
place-items: center;
width: 24px;
height: 24px;
border: 1px solid #7f8a99;
border-radius: 5px;
background: #fff;
color: #3a4250;
font-size: 11px;
cursor: pointer;
}
.x-split-pane__fold:hover {
background: #e3e8ef;
}
.x-split-pane__fold:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: 1px;
}
.x-split-pane__h {
margin: 0 0 8px;
font-size: 12px;
font-weight: 700;
}
.x-split-pane__head .x-split-pane__h {
margin: 0;
}
.x-split-pane__list {
margin: 0;
padding: 0;
list-style: none;
}
.x-split-pane__list li {
display: flex;
align-items: center;
gap: 7px;
padding: 5px 0;
white-space: nowrap;
}
.x-split-pane__list .fa-solid,
.x-split-pane__list .fa-regular {
width: 14px;
color: #5b6472;
}
.x-split-pane__txt {
margin: 0;
color: #3a4250;
line-height: 1.6;
}
/* 仕切り。見える線は1本だが、つかめる幅は12px取る */
.x-split-pane__sep {
position: relative;
display: grid;
place-items: center;
border-left: 1px solid #7f8a99;
border-right: 1px solid #e3e6ea;
background: #fff;
cursor: col-resize;
touch-action: none;
}
/* つかめる範囲を左右に6pxずつ広げ、24px にする(見た目の幅は変えない) */
.x-split-pane__sep::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: -6px;
right: -6px;
z-index: 1;
}
.x-split-pane__grip {
width: 4px;
height: 28px;
border-radius: 2px;
background: #7f8a99;
}
.x-split-pane__sep:hover .x-split-pane__grip,
.x-split-pane__sep:focus-visible .x-split-pane__grip {
background: #1d4ed8;
}
.x-split-pane__sep:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- 一覧と詳細、コードとプレビューのように、2つの面を並べて見比べながら作業させたいとき
- どちらの面を広く取りたいかが人や作業によって違い、利用者に幅を決めさせたいとき
注意点
- 幅が固定の左右分割なら 2カラム(layout/two-column)で足りる。仕切りを動かして幅を変えられることが、この部品の要件
- 最小と最大の幅を決める。0 まで縮められると、パネルがあったことに気づけなくなる
- 決めた幅は localStorage などに覚えておく。開くたびに戻ると毎回やり直しになる
- スマートフォンでは左右に並べる幅が無い。狭い画面では縦に積むかタブで切り替え、仕切りは出さない
- ドラッグ中は本文の文字が選択されないよう、つかんでいる間だけ user-select: none にする
アクセシビリティの要点
- 仕切りは WAI-ARIA APG の Window Splitter の形にする。role="separator" と tabindex="0" でフォーカスできる仕切りにし、aria-valuenow / aria-valuemin / aria-valuemax で主パネルの大きさを数で持つ
- 左右に分ける縦の仕切りには aria-orientation="vertical" を付ける(separator の既定は horizontal)。aria-controls で主パネルを指し、主パネルの名前で aria-labelledby を付ける
- 縦の仕切りは左右の矢印キー、横の仕切りは上下の矢印キーで動かす。Enter で主パネルを畳む/戻す、Home / End で最小/最大へ動かす操作も APG に載っている
- ドラッグだけに頼らない(WCAG 2.2 の 2.5.7 ドラッグ動作)。パネルを畳むボタンや「広げる/狭める」ボタンなど、1回押すだけで幅を変える手段も用意する
- 見える線が細くても、つかめる幅は 24px 相当を確保するか、周りに押せるものを置かない(WCAG 2.2 の 2.5.8 ターゲットのサイズ(最低限))