Inline stylesで擬似クラスが書ける! CSS Hooksが面白い
今回は、最近特に英語圏で注目を集めているCSSライブラリ「CSS Hooks」についてご紹介します!
このライブラリは、CSS Modulesのco-authorでvanilla-extractの作者であるMark Dalgleish氏が大注目しており、自分はこのポストが知るきっかけになりました。
日本語での情報が少なそうだったので、基本的なライブラリの紹介、使い方、仕組みについて触れていこうと思います。
CSS Hooksとは
Inline styles形式で型安全にCSSを記述できるライブラリです。
通常のInline stylesと異なり、擬似クラスやメディアクエリ、コンテナクエリなども同様にInline styles形式で書くことができるというところが特筆すべき点です!これは素のInline stylesにはできない革新的な機能です。
これによってInline stylesの可能性は大きく広がります。
例えば、Inline stylesはHTMLマークアップに直で埋め込まれているため、通常バンドル時に行うようなCSSまわりのビルドステップは不要になります。また、同じ理由でサーバーサイドレンダリング時も問題なく動きます。他のCSSライブラリだと、SSR用にプラスアルファで設定が必要になることが多いですが、このライブラリでは不要になります。シンプルでいいですね。
基本的な使い方
Getting startedをみながら進めていきます。
今回の例ではReactを使用します(公式Docsをみると、SolidJSやPreactもサポートしていそうですね)。
まずパッケージをインストールしたら、以下のように"Hooks"を定義します。
自分が使う分の擬似クラス、メディアクエリなどを定義することであとで吐き出されるCSSが減ります。
// src/css.ts
import { createHooks } from "@css-hooks/react";
export const [hooks, css] = createHooks({
"&:hover": "&:hover",
});
TypeScript5.3以上の場合は以下のように書くこともできます。
import { createHooks } from "@css-hooks/react";
import { recommended } from "@css-hooks/recommended";
export const [hooks, css] = createHooks(
recommended({
pseudoClasses: [":hover"],
})
);
次にこのHooksを、アプリのrootでstyleタグに埋め込みます。
これだけで準備は終わりです!
// src/App.tsx
import { hooks, css } from "./css";
function App() {
return (
<>
<style dangerouslySetInnerHTML={{ __html: hooks }} />
<button
style={css({
backgroundColor: "gray",
"&:hover": {
backgroundColor: "red",
},
})}
>
Hover!
</button>
</>
);
}
要素をhoverすると背景色が変わっていることが確認できますね!
CSS Hooksが擬似クラスなどを扱える仕組み
ここまで説明してきたように、CSS Hooksを使うとシンプルに擬似クラス、メディアクエリなどを書くことができますが、果たしてどういう仕組みになのでしょうか。
結論としては、CSS HooksはCSS Variablesを上手に活用しています。
具体的にはCSS Variablesの第二引数です。
例えば以下のようなCSSを書いたとします。
.hoge {
color: var(--primary, blue)
}
このコードが意味するのは、--primary
がvalidな場合は--primary
を適用し、そうでない場合はblueにfallbackさせるということです。
CSS Hooksはこの仕組みを利用しています。
「基本的な使い方」で紹介した例に戻って考えてみる
こちらが先ほどの例で最終的に主力されるコードになります。
<style>
* {
--mbscpo-0: initial;
--mbscpo-1: ;
}
*:hover {
--mbscpo-0: ;
--mbscpo-1: initial;
}
</style>
<button style="background-color: var(--mbscpo-1, red) var(--mbscpo-0, gray)">
Hover!
</button>
まずは通常時の以下styleに注目してみます。
* {
--mbscpo-0: initial;
--mbscpo-1: ;
}
--mbscpo-0
というクラスに対して、initial
という値が当たっています。
var()
関数で第一引数がinitial
の場合、第二引数にfallbackされるという仕組みなので、
var(--mbscpo-0, gray)
はgrayにfallbackされ、ボタンはグレーになります。
一方、--mbscpo-1: ;
とあるように、--mbscpo-1
には空の値があたっています。この場合、var(--mbscpo-1, red)
に関してはfallbackはされず空の値が優先されるため、結果としてボタンに赤色は適用されません。
hover時は、逆に以下のスタイルが有効になるので、赤色が適用されるようになります。
*:hover {
--mbscpo-0: ;
--mbscpo-1: initial;
}
このようにしてCSS Hooksはスタイルの条件付きトグルを実現しているのですね!🙌
ライブラリ作者のNick Saunders氏のブログでさらに詳しく解説されています↓
CSS Hooks(Inline styles)の懸念点
CSS Hooksはその画期的なアイデアで注目を浴びている一方で、Inline stylesの使用に関して、X上ではいくつかネガティブな指摘が見受けられます。
とりわけ問題視されていそうなのは、パフォーマンスについてです。
Inline Stylesは、Reactの旧公式ドキュメントやGoogleのPageSpeed Insightsのガイドラインで、コードの重複に繋がるなどの理由で使用が推奨されていないので、一般的には避けるのがベターなようです。
これに対して、Nick Saunders氏は、Inline stylesがatomicなCSSよりもパフォーマンスが劣るとはいえ、それほど悪くないと考えていそうです。主張の根拠として、HTMLのパースからペイントまでの時間を測定した結果、Inline stylesはatomicなCSSよりもわずかに30%ほどしか遅くならなかったという記事を参照しています。
これに関しては賛否両論あるかもしれませんが、ブラウザ側がInline stylesのペイントをさらに最適化してくれるような未来があれば、また変わった議論になってくるかもしれないですね!
CSS Hooksのその他の懸念点、改善点については以下のissueにまとまっているので、気になった方は是非みてみてください!
おわりに
今回はCSS Hooksというライブラリをご紹介しました。
このライブラリを含め、たくさんのCSSソリューションが存在する現代のモダンWebフロントエンドが今後どうなっていくか楽しみですね!
Happy Coding & Happy Holidays!🎅
Discussion