ロービングタブインデックス
stimeo--roving
item 群を単一の Tab 停止にし、矢印キーで移動する。
stimeo--roving コントローラは APG のロービングタブインデックス技法を単体のコントローラとして公開します(共有 RovingTabindex util の上に方針を与える層。Focus Scope と FocusTrap の関係と同型)。 item は 1 つだけ tabindex=0(他は -1)で、矢印キーがフォーカスとその Tab 停止を一緒に動かします — horizontal なら ArrowRight/ArrowLeft、vertical なら ArrowUp/ArrowDown、both なら両軸 — Home/End で先頭・末尾へ。wrap=true は端で巡回、wrap=false は端で停止します。リスナはコンテナに委譲し(移動は keydown、クリックやプログラム的なフォーカス到達は focusin で Tab 停止へ同期)、実行時に増減する item も item ごとの結線なしで追従します。tabbable な item が変わると change を発火します。挙動のみ: tabindex とフォーカス移動だけを担い、role 付与・選択・typeahead・アクティベーションは行いません(利用側パターンの責務。ここでは素のアクションボタン)。
グループに一度 Tab で入り、矢印キーでボタン間を移動します。Home/End で先頭・末尾へ。
キーボード操作
| キー | 動作 |
|---|---|
| → / ← | 次/前の item へ(horizontal または both)。 |
| ↓ / ↑ | 次/前の item へ(vertical または both)。 |
| Home / End | 先頭/末尾の item へ(homeEnd が有効なとき)。 |
<%# Roving tabindex demo: the toolbar is a single Tab stop. Tab in once, then the
arrow keys move focus (and the tab stop) between buttons; Home/End jump to the ends
and movement wraps (the defaults). role="toolbar" + the accessible name are the
consumer's — the library owns only tabindex + focus movement, so this is "build your
own toolbar with the roving primitive". The buttons are plain actions; demo.css owns
the look. %>
<div class="roving-demo">
<p class="roving-demo__hint"><%= t("components.roving.demo.hint") %></p>
<div class="roving-demo__group" role="toolbar"
aria-label="<%= t('components.roving.demo.group_label') %>"
data-controller="stimeo--roving">
<button type="button" class="demo-trigger" data-stimeo--roving-target="item">
<%= t("components.roving.demo.items.previous") %>
</button>
<button type="button" class="demo-trigger" data-stimeo--roving-target="item">
<%= t("components.roving.demo.items.play") %>
</button>
<button type="button" class="demo-trigger" data-stimeo--roving-target="item">
<%= t("components.roving.demo.items.next") %>
</button>
<button type="button" class="demo-trigger" data-stimeo--roving-target="item">
<%= t("components.roving.demo.items.shuffle") %>
</button>
<button type="button" class="demo-trigger" data-stimeo--roving-target="item">
<%= t("components.roving.demo.items.repeat") %>
</button>
</div>
</div>
/*
* Presentation-only styles for the roving-tabindex demo. The library owns the single
* Tab stop (it sets tabindex) and focus movement; this CSS only lays out the toolbar
* row. The focused button is highlighted by the shared .demo-trigger :focus-visible
* outline, so the roving focus is visible without any extra rule here.
*/
.roving-demo {
display: flex;
flex-direction: column;
gap: 0.75rem;
align-items: flex-start;
}
.roving-demo__hint {
margin: 0;
color: var(--muted);
}
.roving-demo__group {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--roving"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
item
必須
|
巡回対象の item。矢印キーのロービングで一つの Tab 停止を形成する。 | data-stimeo--roving-target="item" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
orientation
|
矢印キーの軸。horizontal / vertical / both。既定値は horizontal。 |
data-stimeo--roving-orientation-value |
wrap
|
矢印移動が端を越えて巡回するか(false なら端で停止)。既定値は true。 | data-stimeo--roving-wrap-value |
homeEnd
|
Home/End で先頭・末尾へ移動するか。既定値は true。 | data-stimeo--roving-home-end-value |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
tabbable な item が変化したとき { index, item } で発火。 | stimeo--roving:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
tabindex |
item | tabbable な 1 つが 0、他は -1(単一の Tab 停止)。 |