この記事はLIFULL Advent Calendar 2022の14日目です。
フロントエンドエンジニアのえびです。好きな寿司ネタはえんがわです🍣
普段は LIFULL HOME'S の賃貸領域の開発を行っています。
最近、実業務で tailwindcss を使うことが増えました。
命名やCSSファイルとの往復もせずに HTML にガリガリスタイルを当てていけるので非常に効率が良いのですが、状態に応じたスタイルを当てるのにやや苦労していました。
しかし、 アクセシビリティ(a11y) を意識すると快適なスタイリング・堅牢なマークアップが両立できます。
この記事では状態に応じたスタイリング手法を紹介しつつ、アクセシビリティを意識した tailwind modifier をご紹介できればと思います。
状態に応じたスタイリング(modifier)
コーディングを進める上で、インタラクティブな要素には状態に応じたスタイリングが求められます。
たとえばボタンの場合、hover/active/focus
などが挙げられるでしょうか。いわゆる擬似クラスです。
tailwind でこれらのスタイリングを行う場合、 modifier を使います。
button に対して hover:bg-..., active:bg-... focus:ring..
などのように [status]:[class]
の形式で書くことによってその状態のスタイルを当てられます。
また、論理属性に対しても modifier は扱えます。
よく使うものでいうと input の checked:...
が挙げられるでしょうか。
See the Pen Untitled by Moeko Ebisawa (@ebisawa-next) on CodePen.
他にも odd/even や nth-child, only-child など順番に関わるものも標準対応されています。
通常の modifier では解決できないスタイリング
ただし、現代のUIはこれだけだと完全なスタイリングは行えません。
たとえばアコーディオンを details/summary を使わずに自力で実装する場合、「要素が開いている時はこのスタイルを当てたい」という場合に擬似クラス/論理属性では表現することができませんでした。
そのため、別途定義しておいた css を js 側でつけ外しをして要素の開閉を表現する必要がありました。場合によっては子要素のスタイルも丁寧につけ外しをする必要があります😇
これでは tailwind の旨味は薄れてしまいます。
しかし10月ごろに発表された v3.2 で実装された新機能により状況が大きく変わりました。
modifier に関わる3つを紹介していきます。
1. Dynamic group-* and peer-* variants
先の項で申し上げたとおり、tailwind はその性質上従来の CSS のように is-hoge
hoge--mofifier
のように親/兄要素に状態変化クラスをつけて子/弟要素でスタイリングすることが出来ませんでした。
しかし、 v3.2 で実装された Dynamic group-* and peer-variants によってこれらを group/peer でも使えるようになりました。
あるクラスを親/兄要素が持っている場合、 子/弟要素にも影響させることができます。
以前から modifier として擬似クラス .parent:hover .children
.parent:active + .children
などは対応していましたが、
.parent.is-hoge .children
.parent.is-hoge + .children
もいけるようになったのです。
親/兄には状態変化用のクラス is-hoge
を当て、子/弟には (group|peer)-[.is-hoge]:クラス名
のように書けばOKです。
See the Pen Untitled by Moeko Ebisawa (@ebisawa-next) on CodePen.
ただしこのスタイリング方法には欠点があり、状態変化クラスを使って親/兄要素自体へのスタイリングが行えません。
そのため、親/兄要素自体のスタイルを変えたい場合は別途 css を書く必要があります😇
これでは tailwind の旨味は享受しにくいですね。swiper などのライブラリで別途スタイルを当てる時くらいしか用途が思い浮かびません。。
基本的にはこれを使うより、次で紹介する Data Attribute Variants を使っていくのが無難でしょう。
2. Data attribute variants
Data attribute variants は、data属性に応じてスタイルを変えられます。
以前から plugin で書けば同様のことはできましたが、v3.2 で arbitrary value チックに書けるようになり気軽に使えるようになりました。
これは modifier として扱えるため親/兄弟要素にもスタイルをアタッチできますし、子/弟要素に group/peer でスタイルを伝播させることも可能です。ぶっちゃけ Dynamic group-* and peer-* variants の上位互換と言えるでしょう。
書き方は data-[name=value]
の形式で、 group/peer で扱う場合は (group|peer)-data-[name=value]
と書きます。
See the Pen Untitled by Moeko Ebisawa (@ebisawa-next) on CodePen.
3. ARIA attribute variants
ARIA attribute variants = ARIA modifier の標準対応 です。
v3.2 以前から ARIA modifier を使う術はありました。
有志によって開発された tailwindcss-aria-attributes という package を使ったり tailwind.config.js
で addVariants
することで設定できました。
自分もちまちま設定を書いていたのですが、この度公式で設定しやすい環境になりました☺️
この ARIA attribute variants は modifier になりますので、Data Attribute Variants と同様に親/兄要素自体へのスタイリングも可能です。
See the Pen Untitled by Moeko Ebisawa (@ebisawa-next) on CodePen.
v3.2 現在標準対応しているのは以下の8属性ですが、
Modifier | CSS |
---|---|
aria-checked | &[aria-checked=“true”] |
aria-disabled | &[aria-disabled=“true”] |
aria-expanded | &[aria-expanded=“true”] |
aria-hidden | &[aria-hidden=“true”] |
aria-pressed | &[aria-pressed=“true”] |
aria-readonly | &[aria-readonly=“true”] |
aria-required | &[aria-required=“true”] |
aria-selected | &[aria-selected=“true”] |
tailwind.config.js
に aria
を追記することで簡単に設定を増やせます。 addVariants
よりお手軽ですね。
module.exports = {
theme: {
extend: {
aria: {
invalid: 'invalid="true"',
},
},
},
};
// 白い背景のテキストフォーム
<input type="text" value="hoge" class="bg-white aria-invalid:bg-red" />
// 赤い背景のテキストフォーム
<input type="text" value="hoge" class="bg-white aria-invalid:bg-red" aria-invalid="true" />
Data attribute variants と ARIA attribute variants の使い分け
data 属性でスタイルを変えられるなら今までのように js で data 属性をアタッチしてスタイルをハンドリングしてもいいんじゃない?と思われる方も多いと思います。
しかし、ARIA 属性を扱える場面では ARIA attribute variants を使う方が堅牢でメンテナビリティの高いコードになります。
ARIA attribute variants を使った方がよい場面
例えば先の項目で提示したタブパネルですが、これらは Tabs という WAI-ARIA で提示されている tab/tabpanel role が持つべきふるまいを実装しています。
See the Pen Untitled by Moeko Ebisawa (@ebisawa-next) on CodePen.
タブが選択された際に aria-selected
という属性を true
にする必要があるため、おのずと js もそのような実装になります。
/**
* タブにフォーカスします
* @param target - tabTarget
*/
focusTab(target) {
target.setAttribute('tabindex', '0');
target.setAttribute('aria-selected', 'true');
target.focus();
}
blurAllTabs() {
this.tabTargets.forEach((target) => {
target.setAttribute('tabindex', '-1');
target.setAttribute('aria-selected', 'false');
});
}
そしてタブが選択されている場合はタブの背景色を変えたいため、html 側はこんな感じのスタイリングをすることになります。
※わかりやすさのために Stimulus controller アタッチ周りは省略
<button
class="p-2 rounded-t-md border transition-colors hover:bg-yellow-100 aria-selected:bg-yellow-100"
role="tab"
aria-selected="true"
aria-controls="panel-1"
id="tab-1"
tabindex="0"
>
First Tab
</button>
aria-selected:
という直感的でわかりやすい modifier を扱える上に、アクセシビリティを担保した WAI-ARIA に準じた実装を行えるのでかなり堅牢なマークアップになります。
これが data 属性の取り外しだけでスタイリングをやっていると js 側で data 属性をいじっただけで実装が崩壊しかねませんし、なんのための data 属性なのかを js 側で考える必要が出てしまいます。
Data attribute variants を使った方が良い場面
ARIA 属性はそれっぽい状況になったら絶対に使えるというわけではありません。
正しい用法を理解していないとかえってユーザーの妨げに繋がり、アクセシビリティを損ねてしまうことに注意が必要です。
ARIA Authoring Practices Guide (APG) の Patterns を熟読した上で「このモジュールはこのデザインパターンに合致するな」と思ったら取り入れていくのが良いでしょう。
そうでないものの場合は Data Attribute Variants を使うのが無難です。
たとえば input 要素自体にエラーが出ているときは aria-invalid:~
を使えば良いですが、
フォームを囲っている fieldset
や div
に対してスタイルを当てたいぞ!というときは Data Attribute Variants で data-[error="true"]:~
のようにスタイリングするのがよいでしょう🙆♂️
まとめ
- 状態に応じたスタイリングするなら以下の3種類を使うとよい
- 擬似クラス modifier:
[status]:...
- Data Attribute Variants:
data-[name=value]:...
- Aria Attribute Variants:
aria-[status]:...
- 擬似クラス modifier:
- 基本は擬似クラスの modifier を使おう
- WAI-ARIA に則ったデザインパターンの場合は Aria Attribute Variants を使おう
- それ以外の場合は Data Attribute Variants を使おう
この記事がよりよいコーディングライフの一助になれば幸いです🥳