1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

Chrome 150から、HTML属性の focusgroup が使えるようになりました。

これまでツールバーやタブリストなどの複合ウィジェットでキーボードナビゲーションを実装するには、JavaScriptで「roving tabindex」パターンを手書きする必要がありました。focusgroup 属性を使えば、そのロジックをJavaScriptなしでHTMLだけで宣言的に書けます。

これまでの課題

WAI-ARIAのベストプラクティスでは、ツールバーやメニューなどの複合ウィジェットにはroving tabindexを実装することが推奨されています。

roving tabindexとは「現在フォーカスされている要素だけ tabindex="0" にし、他の要素は tabindex="-1" にして、矢印キーで移動するたびにJavaScriptで付け替える」パターンです。

const items = document.querySelectorAll('[role="tab"]');

items.forEach((item, index) => {
  item.addEventListener('keydown', (e) => {
    let next;
    if (e.key === 'ArrowRight') next = items[(index + 1) % items.length];
    if (e.key === 'ArrowLeft') next = items[(index - 1 + items.length) % items.length];
    if (next) {
      item.setAttribute('tabindex', '-1');
      next.setAttribute('tabindex', '0');
      next.focus();
    }
  });
});

シンプルな例でもこれだけのコードが必要で、折り返しや方向制御などを加えると一気に複雑になります。

focusgroup属性で解決

focusgroup 属性を親要素に付けるだけで、ブラウザが矢印キーナビゲーションを自動で処理してくれます。

<div focusgroup="toolbar" aria-label="書式設定">
  <button>太字</button>
  <button>斜体</button>
  <button>下線</button>
</div>

これだけで

  • 左右矢印キーでボタン間を移動できる
  • Tabキーで初回フォーカス(または最後にフォーカスしていた要素)に戻る
  • roving tabindexのJavaScriptが不要

基本的な使い方

ビヘイビアトークン

ウィジェットの種類に応じたプリセットが用意されています。それぞれデフォルトの方向や折り返しが設定されています。

矢印キーの方向 折り返し
toolbar 左右 なし
tablist 左右 あり
menubar 左右 あり
menu 上下 あり
radiogroup 全方向 なし
listbox 全方向 なし
<!-- ツールバー -->
<div focusgroup="toolbar" role="toolbar" aria-label="書式設定">
  <button>太字</button>
  <button>斜体</button>
  <button>下線</button>
</div>

<!-- タブリスト(左右+折り返し) -->
<div focusgroup="tablist" role="tablist">
  <button role="tab">タブ1</button>
  <button role="tab">タブ2</button>
  <button role="tab">タブ3</button>
</div>

<!-- 縦メニュー(上下+折り返し) -->
<ul focusgroup="menu" role="menu">
  <li role="menuitem">メニュー1</li>
  <li role="menuitem">メニュー2</li>
  <li role="menuitem">メニュー3</li>
</ul>

修飾子トークン

ビヘイビアトークンに組み合わせてデフォルト挙動を上書きできます。

方向の指定:

  • inline — 左右矢印キーのみ反応
  • block — 上下矢印キーのみ反応

折り返しの制御:

  • wrap — 末尾まで進んだら先頭にループ
  • nowrap — ループを無効化

フォーカス記憶の制御:

  • nomemory — 最後にフォーカスした要素を記憶しない(Tabで常に先頭に戻る)
<!-- toolbarに折り返しを追加 -->
<div focusgroup="toolbar wrap" aria-label="書式設定">
  <button>太字</button>
  <button>斜体</button>
  <button>下線</button>
</div>

<!-- tablistのフォーカス記憶を無効化 -->
<div focusgroup="tablist nomemory" role="tablist">
  <button role="tab">タブ1</button>
  <button role="tab">タブ2</button>
</div>

グループからの除外

特定の要素をfocusgroupのナビゲーション対象から外したいときは none を使います。

<div focusgroup="toolbar">
  <button>太字</button>
  <button>斜体</button>
  <span focusgroup="none">
    <button>ヘルプ</button>
  </span>
</div>

デモ

実際の動きを確認してみてください。

See the Pen Focusgroup by degudegu2510 | Qiita (@degudegu2510) on CodePen.

ブラウザサポート

Chrome 150以降でサポート。現時点ではChrome/Edgeのみで、FirefoxとSafariは未対応です。

まとめ

focusgroup 属性を使うことで、これまでJavaScriptで手書きしていたroving tabindexのロジックをHTMLだけで表現できます。WAI-ARIAのロールに対応したプリセット(toolbartablistmenu など)が用意されているので、意味的にも正しいマークアップと組み合わせやすいのが嬉しいポイントです。

アクセシブルなキーボードナビゲーションの実装コストが下がるのは、Webにとって大きな前進だと思います。ぜひ試してみてください!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?