📑

Tabについて - React Ariaの実装読むぞ

2024/12/16に公開

こんにちは、フロントエンドエンジニアの mehm8128 です。
今日は Tab について書いていきます。

https://react-spectrum.adobe.com/react-aria/useTabList.html

使用例

ドキュメントからそのまま取ってきています。

function Tabs(props) {
  let state = useTabListState(props);
  let ref = React.useRef(null);
  let { tabListProps } = useTabList(props, state, ref);
  return (
    <div className={`tabs ${props.orientation || ""}`}>
      <div {...tabListProps} ref={ref}>
        {[...state.collection].map((item) => (
          <Tab key={item.key} item={item} state={state} />
        ))}
      </div>
      <TabPanel key={state.selectedItem?.key} state={state} />
    </div>
  );
}

function Tab({ item, state }) {
  let { key, rendered } = item;
  let ref = React.useRef(null);
  let { tabProps } = useTab({ key }, state, ref);
  return (
    <div {...tabProps} ref={ref}>
      {rendered}
    </div>
  );
}

function TabPanel({ state, ...props }) {
  let ref = React.useRef(null);
  let { tabPanelProps } = useTabPanel(props, state, ref);
  return (
    <div {...tabPanelProps} ref={ref}>
      {state.selectedItem?.props.children}
    </div>
  );
}

本題

APG はこちらです。
https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/

role とaria-属性

1 つ 1 つのタブボタンはtabrole、それらを囲っているものがtablistrole、タブ選択時にコンテンツが表示される領域がtabpanelrole です。
全てのtabrole にはそれぞれに対応するtabpanelの id をaria-controlsに指定し、さらにアクティブなtabrole にはaria-selected="true"をつけます。

キーボード操作

Tab キーで現在アクティブなタブにフォーカスが当たり、矢印キーでタブの移動ができます。

また、タブにフォーカスが当たっているときに Tab キーを押した場合、タブパネルがその中に Tab キーによってフォーカス可能な要素を含んでいればその要素にフォーカスが当たります。Tab キーによってフォーカス可能な要素を含んでいなければタブパネル自体がtabindex="0"になってタブパネル全体にフォーカスが当たります。

https://github.com/adobe/react-spectrum/blob/b3a4d6c1134aae882aa1dcfce64efba1d8f4308d/packages/%40react-aria/tabs/src/useTabPanel.ts#L31-L34

(注意: tabbaletabbingは Tab キーでフォーカスできる、Tab キーを押す、などの意味で、tabはタブコンポーネントを意味しています)

タブの配置方向

縦に配置する場合と、左右反対に配置するパターンがあります。

縦に配置する場合はaria-orientation="vertical"をつけ、上下矢印キーでタブを移動できるようにします。
また、右から左に読む言語を利用している場合、最初のタブが一番右、最後のタブが一番左に配置されるようにする必要があり、さらにキーボード操作も左右逆にします。ドキュメントのデモ部分で devtools からdir="ltr"dir="rtl"にすると体験できます。

Pointer Cancellation

過去に こんな issue がありました。

https://github.com/adobe/react-spectrum/issues/4336

WCAG の Success Criterion 2.5.2 Pointer Cancellationに準拠せず、pointerdown のタイミングでしかタブの選択ができなかった時期があったようです。今回のケースは 4 つ挙げられているパターンのうちの「Abort or Undo」に当たると思います。
そこで、useTab: adds support for shouldSelectOnPressUp #4342shouldSelectOnPressUpprops がサポートされ、間違えてタブをクリック(pointerdown)してしまったときでも pointerup する前にタブからカーソルを移動すればタブの選択をキャンセルすることができるようになりました。

aria-controlsについて

最初に述べたaria-controlsについて、コントロールする側とされる側の要素を紐づけるために付与するべきなのは分かっているのですが、スクリーンリーダーでの読み上げに特に影響しているわけではなさそうで、サービスの利用者目線で具体的に何かメリットがあるのか気になっていました。そこで、1 つ記事を見つけたので共有します。

https://www.makethingsaccessible.com/guides/aria-controls-vs-aria-owns/

aria-controlsaria-ownsの違いを説明する記事なのですが、So, how are they different?aria-controlsについて説明されています。特に「Does ARIA Controls have good support?」のセクションでスクリーンリーダーなどのサポート状況について述べられていて、Accessibility Supportを確認すると JAWS というスクリーンリーダーでのみaria-controlsに対してなんらかのサポートを行っていることが説明されています。とはいえアクセシビリティツリーにはaria-controlsの値が公開されているので、今後他のスクリーンリーダーでもサポートされる可能性はあり、そのときのためにつけておくのがよさそうということらしいです。
W3C や NVDA のリポジトリで、aria-controlsに対してどのようなサポートをするべきか議論されているような issue も見つけたので読んでみるといいかもしれません。

https://github.com/w3c/aria/issues/995
https://github.com/nvaccess/nvda/issues/8983

まとめ

明日の担当は @mehm8128 さんで、番外編 Focus Management API について(概要編)の記事です。お楽しみにー

Discussion