💅

WAI-ARIA 準拠には CSS Modules が最適という話

2021/06/18に公開

CSS と WAI-ARIA

「WAI-ARIA」はブラウザー・支援技術が認識できる「意味」を注釈することで、ユーザーの理解を助ける技術です。この与えられた注釈は CSS にも共有され、意味をたよりに装飾の手がかりとすることができます。

セマンティックな「状態」表現

特別な理由があり「セマンティックではない」以下の様なマークアップを行った場合をみてみます。spanタグの class 名に btnの名前が付与されていますが、ブラウザー・支援技術は、以下をボタンだと認識できません。

<span class="btn">Mute Audio</span>

これにrole="button"を与える事で、このspanタグはボタンであることが認識されます。またaria-pressed="true"が注釈されています。これで「ボタンが押されている」という状態を、ブラウザー・支援技術に伝えることができます。

<span role="button" aria-pressed="true">Mute Audio</span>

この「aria-pressed」を切り替える機能実装を通して、なぜ CSS Modules が WAI-ARIA 準拠に最適なのかを見ていきます。

CSS in JS と WAI-ARIA

CSS in JS では下記コードのisPressed のように、tagged template literals の中で Props を参照することができます。aria-pressedisPressedが重複していますね。CSS in JS 向け・HTML 向けに、それぞれ値を与えてしまっています。この 2 者には別の値を与えることも可能であり、乖離する可能性さえあります。

const Button = styled.span<{ isPressed: boolean }>`
  border: 2px solid ${({ isPressed }) => (isPressed ? "#00F" : "#000")};
`;
const Component = ({ isPressed }: Props) => (
  <Button
    role="button"
    aria-pressed={isPressed} // <- 重複
    isPressed={isPressed} // <- 重複
  >
    Mute Audio
  </Button>
);

次の様に tagged template literals の中で props["aria-pressed"]を参照すれば、この重複は回避できます。しかし見てのとおり、Button の内部に大量のロジックを与えなければけません。aria-pressed が取りうる値は'aria-pressed'?: boolean | "true" | "false" | "mixed" | undefinedであるため、この様な条件分岐になります。

const Button = styled.span`
  border: 2px solid ${(props) =>
      props["aria-pressed"] === true || props["aria-pressed"] === "true"
        ? "#00F"
        : "#000"};
`;
const Component = ({ isPressed }: Props) => (
  <Button role="button" aria-pressed={isPressed}>
    Mute Audio
  </Button>
);

aria 属性をセレクタとして指定するとどうでしょうか。&[aria-pressed="true"] のような指定があれば、JavaScript の介入は必要ありません。筆者の考える WAI-ARIA に準拠したスタイリングとはこのようなものです。

const Button = styled.span`
  border: 2px solid #000;
  &[aria-pressed="true"] {
    border-color: #00f;
  }
`;
const Component = ({ isPressed }: Props) => (
  <Button role="button" aria-pressed={isPressed}>
    Mute Audio
  </Button>
);

この書き方の欠点は、CSS 詳細度が「10」でなくなってしまうため、詳細度と再度向き合わなければいけないことです(この小さなスコープにおいては、詳細度は大きな問題にならないとは思いますが)

どちらかというと問題は、props に依存する CSS in JS らしい書き方を封じられてしまうことです。強力な機能なだけに、望ましくないと感じる方も多いのではないでしょうか。

CSS Modules と WAI-ARIA

この書き方をチームのガイドラインとしたい場合は、CSS Modules が最適です。分離されたファイルは Pure CSS に限りなく近く、props を参照する・しないの議論は、ここでは発生しません。

import styles from "./style.module.scss";
const Component = ({ isPressed }: Props) => (
  <span role="button" aria-pressed={isPressed} className={styles.btn}>
    Mute Audio
  </span>
);
.btn {
  border: 2px solid #000;
  &[aria-pressed="true"] {
    border-color: #00f;
  }
}

CSS in JS と比較すると表現力や型安全に劣りますが、その代わりに得られるものがあります。

  • アクセシブルな状態とスタイルが乖離しないこと
  • セマンティックなマークアップ・スタイリングが促されること
  • Pure CSS に限りなく近いこと

アクセシビリティ・セマンティクス・スタイリングは、不可分な関係にあるのではないでしょうか。

参考文献

本稿の例示は極端なものでしたが、MDN に WAI-ARIA に準拠したスタイリング例がいくつか掲載されているので、参考にしてみると良いと思います。

https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/checkbox_role
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Switch_role

W3C でも同様に触れられている文献があったため、引用します。

https://www.w3.org/TR/wai-aria-1.1/#introstates

Most modern user agents support CSS attribute selectors ([css3-selectors]), and can allow the author to create UI changes based on WAI-ARIA attribute information, reducing the amount of scripts necessary to achieve equivalent functionality. In the following example, a CSS selector is used to determine whether or not the text is bold and an image of a check mark is shown, based on the value of the aria-checked attribute.

Discussion