🧷

ReactとCSS in JSとWAI-ARIAの付き合い方

2021/06/21に公開

https://zenn.dev/takepepe/articles/semantic-waiaria-css
の記事に対するアンサーソング的な記事になります。

WAI-ARIAが適切に設定されているアプリはアクセシビリティが高まります。

WAI-ARIAというとスクリーンリーダーを使っているユーザーのための機能だというような風潮があったりなかったりしますが、僕のような可能な限りの操作をキーボードで行いたいタイプの人間に対する恩恵も高まります。アクセシビリティをおざなりにしてUXを語るなかれ、とても大事な機能です。
Aria最高、LJL最強のMIDレーナー。

CSS in JS(styled-components)でWAI-ARIAする場合のベストプラクティス

CSS in JSといっても色々なライブラリがあるのでそれぞれ書き方は変わってしまいますが、参照記事ではstyled-componentsをサンプルに使っているのでそれに準拠します。

const AriaButtonSample = styled.span.attrs(props => ({
  role: 'button',
  'aria-pressed': props.isPressed,
}))`
  border: 1px solid ${({isPressed}) => isPressed ? '#00f' : '#000'};
  padding: 6px 8px;
  cursor: pointer;
`;

// render
  <AriaButtonSample isPressed={true}>Aaaaaa</AriaButtonSample>

.attrs は微妙にマイナーな機能ですが、与えられた props からさらにpropsをmapできる機能です。
もちろんdefaultProps的なこともできますので、ここで role="button" も与えています。

ここで主張したいことの本質はrolearia-*の隠蔽、及びaria-pressedの状態のpropsからの算出です。

以下で補足しますが、筆者はここでのスタイルの与え方は条件分岐で書こうがnested selectorで書こうかどっちでも良いと考えています。
コンポーネントに閉じている限り、可読性にも保守性にも大差がないからです。

Reactのコンポーネントに外からaria-*属性を与えてはいけない

aria-*を外側から与えなければならないようなコンポーネントは設計が破綻しているというのが筆者の立場です。

WAI-ARIAはざっくり分けて対象となるコンテンツの:

  • 役割を示すrole
  • 状態を示すaria-*

から構成されていますが、これはReactにおいてはコンポーネント名とprops(およびstate)が担当しています。
クラスの設計においてカプセル化というものは非常に基礎的な方針ですが、コンポーネントにおいても同じく可能な限りの情報は外部から隠蔽されるべきです。

つまり上記の例でいえば、このボタンコンポーネントは Button というコンポーネント名、および onClick などの公開されているpropsからボタンのUIとして振る舞うという事実のみを公開すれば十分だということですね。
むしろ rolearia- などの標準のHTML属性は隠蔽して書き換え不可能にすべきです。

普通のプロジェクト内で作るコンポーネントは公開しているpropsが少ない、そして状態変化の少ない、つまり汎用性に欠けるほど基本的には優秀です。
一番汎用的なのはdivとspanですからね。

まとめると、コンポーネント設計においてはまず「Reactコンポーネントとしてのセマンティクス」が存在しており、そこをDOMに落とし込むプロセスの中に適切なWAI-ARIAの付与が内包されているが、それらは実装の詳細であり外部に公開されるべきではないということです。

CSS Modules vs CSS in JS

この章は本題とあまり関係のない余談です。

最近また何やら盛り上がってますが、こういう記事をわざわざ書くくらいなので筆者はCSS in JS推しです。
が、ぶっちゃけ両者に議論に値するほどの機能的格差があるとはおもっていません。自転車置き場の議論のたぐいですね。遊びでプロレスをやる分には面白いけど真面目にやるのはあほらしいです。
コピペせずに、ちゃんと理解して自力でビルド周りの設定を書けるメンバーがいるなら好きな方使えばいいとおもいます。

ちなみに僕がCSS Modulesを嫌いなのはスタイルシートにこだわるなら別にBEMでいいじゃんとおもってるから

import styles from "./aaa.css";

というコードの意味がビルド設定によって大きく変わってしまうから、というのが大きいです。
あとはビルドの設定が云々とかはあるんですが、その辺は最近のCSS in JSでパフォーマンスを詰めていくと段々と変わりなくなるので。

あともうひとつ、Reactが出た当初は一部に「おまえさんPHPに先祖帰りしてんぞwwww」と心ない煽られを食らっていたのをみなさん覚えてますでしょうか。
そのときの開発チームの返答は
「確かにロジックとテンプレートが合体してるのはダサいかもしれないし、addEventListenerじゃなくてonClickで埋め込んでるのも今風じゃないかもしれない。でもコンポーネントという単位でみるとこれらは本質的に同一なので同じ箇所におくのは理にかなっている」
みたいな感じでした(うろおぼえ)。

ReactDOMのコンポーネントはWebアプリのUIのコンポーネントなので、スタイルも含めて一つのオブジェクトですよね。ということで僕は基本的にファイルを分けずに同じコンポーネントにスタイルを書くスタイルを好みます。
Zenに従って副作用を排除したプログラミングスタイルで統一されていれば、一つのモジュールの行数が多少膨れ上がっても保守性に影響を与えることはありません(確かElmも同じような主張をしていましたね)。むしろ一つの関心ごとに関わるものが一つにまとまっているのはとても自然で心地が良いですよ。

まとめ

  • CSS in JSはいろいろある
    • styled-components の.attrs()は便利
    • 筆者は @emotion/react のほうが好き
  • コンポーネントライブラリを作るのでもない限り、コンポーネントは汎用性が低い方が優秀
  • ReactコンポーネントはARIA系の属性を外部に公開しないほうがいい
  • どっち使っても大体同じことができる

以上です。

Discussion