トグルグループ
stimeo--toggle-group
押した状態を持つボタンの集まり。1 つだけ選ぶ形にも、いくつでも選べる形にもできます。
押した状態を持つボタンを並べたまとまりです。押されているかどうかは読み上げに伝わり、ボタンの名前は変わりません。名前が入れ替わると、読み上げを頼りにしている人が混乱するためです。まとまり全体で Tab は 1 回止まるだけで、中は矢印キーで移動します。1 つだけ選ぶ形と、いくつでも選べる形を選べます。使えないボタンは移動先から外れ、無効だと伝えたいボタンはフォーカスは当たるが押せません。あとから足したボタンも、個別の設定なしで動きます。利用者が押したときだけイベントが出ます。
実行中
複数選択
単一選択
利用不可の項目
キーボード操作
| キー | 動作 |
|---|---|
| Space / Enter | フォーカス中のボタンをトグル。 |
| → / ↓ | 次のボタンへフォーカス移動(ループ)。RTL では左右のみ入れ替わる。 |
| ← / ↑ | 前のボタンへフォーカス移動(ループ)。RTL では左右のみ入れ替わる。 |
| Home / End | 先頭 / 末尾のボタンへフォーカス移動。 |
<%# Markup for the toggle-group demo: multiple selection, single selection, and
unavailable items. The group delegates click and keydown, so the items carry
no data-action of their own. %>
<div class="toggle-group-demo-suite">
<section class="toggle-group-demo__example">
<h3 class="toggle-group-demo__heading" id="toggle-group-multiple-heading">
<%= t("components.toggle_group.demo.multiple_heading") %>
</h3>
<div class="toggle-group-demo" role="group"
aria-labelledby="toggle-group-multiple-heading"
data-controller="stimeo--toggle-group">
<button type="button" class="toggle-group-demo__item" aria-pressed="false" tabindex="0"
aria-label="<%= t('components.toggle_group.demo.bold') %>" data-value="bold"
data-stimeo--toggle-group-target="item">
<svg class="demo-icon" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M6 4h8a4 4 0 0 1 0 8H6z"></path>
<path d="M6 12h9a4 4 0 0 1 0 8H6z"></path>
</svg>
</button>
<button type="button" class="toggle-group-demo__item" aria-pressed="false" tabindex="-1"
aria-label="<%= t('components.toggle_group.demo.italic') %>" data-value="italic"
data-stimeo--toggle-group-target="item">
<svg class="demo-icon" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<line x1="19" y1="4" x2="10" y2="4"></line>
<line x1="14" y1="20" x2="5" y2="20"></line>
<line x1="15" y1="4" x2="9" y2="20"></line>
</svg>
</button>
<button type="button" class="toggle-group-demo__item" aria-pressed="false" tabindex="-1"
aria-label="<%= t('components.toggle_group.demo.underline') %>" data-value="underline"
data-stimeo--toggle-group-target="item">
<svg class="demo-icon" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M6 3v7a6 6 0 0 0 12 0V3"></path>
<line x1="4" y1="21" x2="20" y2="21"></line>
</svg>
</button>
</div>
</section>
<section class="toggle-group-demo__example">
<h3 class="toggle-group-demo__heading" id="toggle-group-single-heading">
<%= t("components.toggle_group.demo.single_heading") %>
</h3>
<div class="toggle-group-demo" role="group"
aria-labelledby="toggle-group-single-heading"
data-controller="stimeo--toggle-group"
data-stimeo--toggle-group-mode-value="single">
<button type="button" class="toggle-group-demo__item" aria-pressed="true" tabindex="0"
aria-label="<%= t('components.toggle_group.demo.align_left') %>" data-value="left"
data-stimeo--toggle-group-target="item">
<svg class="demo-icon" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" aria-hidden="true">
<path d="M4 6h16M4 10h11M4 14h16M4 18h9"></path>
</svg>
</button>
<button type="button" class="toggle-group-demo__item" aria-pressed="false" tabindex="-1"
aria-label="<%= t('components.toggle_group.demo.align_center') %>" data-value="center"
data-stimeo--toggle-group-target="item">
<svg class="demo-icon" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" aria-hidden="true">
<path d="M4 6h16M6 10h12M4 14h16M7 18h10"></path>
</svg>
</button>
<button type="button" class="toggle-group-demo__item" aria-pressed="false" tabindex="-1"
aria-label="<%= t('components.toggle_group.demo.align_right') %>" data-value="right"
data-stimeo--toggle-group-target="item">
<svg class="demo-icon" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" aria-hidden="true">
<path d="M4 6h16M9 10h11M4 14h16M11 18h9"></path>
</svg>
</button>
</div>
</section>
<section class="toggle-group-demo__example">
<h3 class="toggle-group-demo__heading" id="toggle-group-disabled-heading">
<%= t("components.toggle_group.demo.disabled_heading") %>
</h3>
<div class="toggle-group-demo" role="group"
aria-labelledby="toggle-group-disabled-heading"
data-controller="stimeo--toggle-group">
<button type="button" class="toggle-group-demo__item toggle-group-demo__item--wide"
aria-pressed="false" tabindex="0" data-value="available"
data-stimeo--toggle-group-target="item">
<%= t("components.toggle_group.demo.available") %>
</button>
<button type="button" class="toggle-group-demo__item toggle-group-demo__item--wide"
aria-pressed="false" tabindex="-1" data-value="native-disabled" disabled
data-stimeo--toggle-group-target="item">
<%= t("components.toggle_group.demo.native_disabled") %>
</button>
<button type="button" class="toggle-group-demo__item toggle-group-demo__item--wide"
aria-pressed="false" tabindex="-1" data-value="aria-disabled" aria-disabled="true"
data-stimeo--toggle-group-target="item">
<%= t("components.toggle_group.demo.aria_disabled") %>
</button>
</div>
</section>
</div>
/*
* Presentation-only styles for the toggle-group demo.
* The pressed state is expressed via [aria-pressed="true"] (set by the library).
*/
.toggle-group-demo-suite {
display: grid;
gap: 1.5rem;
}
.toggle-group-demo__example {
display: grid;
justify-items: start;
gap: 0.5rem;
}
.toggle-group-demo__heading {
margin: 0;
color: var(--fg);
font-size: 0.9375rem;
font-weight: 700;
}
.toggle-group-demo {
display: inline-flex;
gap: 0.25rem;
padding: 0.25rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 0.5rem;
}
.toggle-group-demo__item {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
color: var(--fg);
background: transparent;
border: 1px solid transparent;
border-radius: 0.375rem;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.toggle-group-demo__item--wide {
width: auto;
min-width: 2.25rem;
padding-inline: 0.75rem;
font: inherit;
font-size: 0.875rem;
}
.toggle-group-demo__item:hover {
background: var(--surface-subtle);
}
.toggle-group-demo__item:disabled,
.toggle-group-demo__item[aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
.toggle-group-demo__item:disabled:hover,
.toggle-group-demo__item[aria-disabled="true"]:hover {
background: transparent;
}
.toggle-group-demo__item:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Fill with the accent color while pressed. */
.toggle-group-demo__item[aria-pressed="true"] {
color: var(--white);
background: var(--accent);
border-color: var(--accent);
}
.toggle-group-demo__item .demo-icon {
width: 1.1rem;
height: 1.1rem;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--toggle-group"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
item
必須
|
<button type="button"> または非インタラクティブな role="button"。押下状態は aria-pressed。 |
data-stimeo--toggle-group-target="item" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
mode
|
single(押下0〜1、他は解除)か multiple(任意数、既定)。 | data-stimeo--toggle-group-mode-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
onKeydown
|
任意の項目別 action。矢印・Home/End はロービングし、汎用ホストは Space/Enter で活性化。 | stimeo--toggle-group#onKeydown |
toggle
|
任意の項目別 action。未指定でも click / keydown のイベント委譲で動作。 | stimeo--toggle-group#toggle |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
利用者操作時のみ発火。detail は DOM 順の { value, pressed, values }。 |
stimeo--toggle-group:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-pressed |
アイテム | "true" / "false"(押下状態)。 |
tabindex |
アイテム | アクティブが 0、他は -1(ロービング)。 |