WAI-ARIA 準拠には CSS Modules が最適という話
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-pressed
とisPressed
が重複していますね。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 に準拠したスタイリング例がいくつか掲載されているので、参考にしてみると良いと思います。
W3C でも同様に触れられている文献があったため、引用します。
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