Twitterでこういう発言を見かけまして
- Tailwind CSSはデザインに凝ってるサイトでは使えない
- こだわりが無い場合に向いている
は?何いってんの?
って思ったので、自分がいろいろ試した結果、Tailwind CSSを選んだ話を書きます。
はじめに
以前、Tailwind CSSは結構いいぞって話を書いたんですが、この記事の立ち位置的にはその続きみたいなものなので、以下の記事を始めにご参照いただけるとより分かりやすいかもしれないです。
この記事では、前回記事を書いた後、個人仕事でWebサイトをGatsbyで作り、その中で、どうやってCSSを書くのが良いのか模索した結果、自分はこれを選んだっていうのを、同じUIを色々な方法で書き比べたコードを並べつつ、どうのこうの筆者の考えを述べていきます。
その仕事はほとんど筆者が「まかせてくださいよーいい感じに作りますよー。デザインそろってないっすね。揃えます!」ってノリで、自分が好き勝手、やりたいようにやったやつです。ちなみにGatsby + Prismicでやりました。Movable Typeでやるとか言ってたので辞めさせてそれにしました。
はじめに、最終的にCSSを書くために使った技術を言っておくと
- 8割方Tailwind CSS
- Tailwind CSSで書きづらい部分は @emotion/css + twin.macro
です。
サンプルコード
なんか書いてたらどんどん長くなってしまって、うわっ、私の記事長すぎ……って思ってるのですが、この記事を書くために書いたコードは以下に置いておきました。
create-react-app で作ってて、以下にデプロイしてあります
サンプルコードって言っても、ほぼひたすら同じUIを別の方法で書いたやつが並んでるだけですが。このページに並んでいるのは、
- スモールスクリーン時は縦にテキストと画像が積まれる
- ワイドスクリーン時は横にテキストと画像が並ぶ
という、CSSを書いてる人であれば100万回ぐらい書いたであろうものを、色んな方法で書いたものです。ブラウザを縮めたり広げたりしてご確認頂ければと思います。
コンポーネントはこのディレクトリに一通り入ってます。
ピュアにTailwind CSSで書いたモノ
それでははじめましょー。ReactでどうCSS設計をしたら良いのかの旅ってことで。
とりあえず初めは、純粋にTailwind CSSで書いたらこうなるぞというものです。
コードとしてはこんな感じになります。
<div className="lg:ml-[-40px]">
<div className="lg:flex w-full">
<div className="pb-[20px] space-y-[20px] lg:pl-[40px] lg:w-1/2">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</div>
<div className="lg:pl-[40px] lg:w-1/2">
<img className="block w-full" src="https://placekitten.com/400/300" alt="" />
</div>
</div>
</div>
まず、前回の記事でも書いたことなんですが、ReactやらVueなりで書くわけでもナシに、HTMLとCSSを手書きで書いているような状況だとします。
このUIが1回だけ出てくるんであればこれでいいんですが、このUIなんて汎用的に使われそうなやつですし、2回以上出てくる時に、これで書いてしまうとキツいですね。
Tailwind CSSで書けば、CSS書かなくてもスタイル当てられて楽だ〜ってなるんですが、これをコピペして画面を増やしていったら、使われてる箇所を全部直して回らないといけないです。地獄……。なので、HTML+CSSだけ書いているような人にとっては
「うーんTailwind CSSってこういうやつか〜。私は使わないなー」
って思うんじゃないかと思います。まーそれはそうですね。自分も手でHTMLを量産していくようなケースにおいては、Tailwind CSSは選ばねーなと思います。
救世主BEM
そんなこんなでHTML+CSSを書く人たちを救ってくれたと言うか、あ!これこれ!これいいね!って流行ったのがBEMなわけなんですが、そのBEMで書くと、以下のような感じになります。
<div className="BemBlock">
<div className="BemBlock__Inner">
<div className="BemBlock__TextWrapper">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</div>
<div className="BemBlock__ImgWrapper">
<img className="BemBlock__Img" src="https://placekitten.com/400/300" alt="" />
</div>
</div>
</div>
/* BemBlock */
.BemBlock__TextWrapper {
padding: 0 0 20px;
}
.BemBlock__TextWrapper > * + * {
padding: 20px 0 0;
}
.BemBlock__Img {
display: block;
width: 100%;
}
@media (min-width: 768px) {
.BemBlock {
margin: 0 0 0 -40px;
}
.BemBlock__Inner {
display: flex;
width: 100%;
}
.BemBlock__TextWrapper {
padding: 0 0 0 40px;
width: 50%;
}
.BemBlock__TextWrapper > * + * {
padding: 20px 0 0;
}
.BemBlock__ImgWrapper {
padding: 0 0 0 40px;
width: 50%;
}
}
BEMについてはここで突っ込んで書かないですが、これでコンポーネントっぽくできたねーという感じです。ただ、色々問題があります。
まずひとつは、このCSSが書かれているのが、サイト全体で読み込んでいるCSSであるということ。色々やりようはありますが、基本的には、多くの画面において、その画面で使っていないCSSのルールが大量に含まれた巨大CSSを読み込むことになります。
そしてもうひとつは、これはコンポーネントらしきモノにすぎないということです。スタイルを変えたかったら、確かにCSSをいじればそれで済むんですが、変えたい内容によってはdivやらspanやらを追加したり、要素の順番を入れ替えたり追加したりしないとならないです。コンポーネントを夢見たHTMLとCSSって感じです。今からすると。
まぁ、色々問題はあるものの、みんながそれぞれの考え方で好き好きにCSSを書いていた時代と比べると、こんな風にわかりやすいルールが広まって、共通言語みたいになったというのは、すごいことだなーとは思います。自分も手書きでHTMLをガンガン書いていくような状況では、積極的にBEMを選ぶと思います。
BEM + Tailwind CSS
そんなBEMですが、このBEMベースの設計にTailwind CSSを混ぜることは出来ます。コードとしてはこんな感じです。HTMLはさっきとほぼ一緒な感じ。
<div className="BemTailwindBlock">
<div className="BemTailwindBlock__Inner">
<div className="BemTailwindBlock__TextWrapper">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</div>
<div className="BemTailwindBlock__ImgWrapper">
<img className="BemTailwindBlock__Img" src="https://placekitten.com/400/300" alt="" />
</div>
</div>
</div>
/* BemTailwindBlock */
.BemTailwindBlock {
@apply lg:ml-[-40px];
}
.BemTailwindBlock__Inner {
@apply lg:flex w-full;
}
.BemTailwindBlock__TextWrapper {
@apply pb-[20px] space-y-[20px];
@apply lg:pl-[40px] lg:w-1/2;
}
.BemTailwindBlock__ImgWrapper {
@apply lg:pl-[40px] lg:w-1/2;
}
.BemTailwindBlock__Img {
@apply block w-full;
}
どうでしょう、だいぶスッキリしてます。
この場合のTailwind CSSの役割としては、色々と短く書けるってことですね。あと、Tailwind CSSではデザインシステム的なことをすることができて、それを活かせるというのもあるんですが、これについては後で書きます。
こういう使い方はナシじゃないんですが、Tailwind CSSの指向している「ユーティリティーファースト」とは違うんですよね。はじめはこういう風にTailwind CSSを使うのは違うんですよって思って書いたんですが、なんか実際書いてみると悪くはないですね……。手でHTMLを書いていく場合にはまぁこういう使い方を検討してみてもいいんではないでしょうか。
とはいえ!前述のBEMの問題は何も解決されていないです。このコードでapplyしてる部分はただTailwind CSSで定義されているプロパティと値のセットに置き換わるだけなので、実質、Sass的に言うと「便利なmixinがいっぱい使える」みたいな状態ですね。
なので、こういう使い方を想像されているなら、
「うーん、学習コストそれなりにあるし、別にいっかな〜〜〜」
は、まぁそうだろうなとは思います。
これは主観ですが、Tailwind CSSを書くのって、フツーにCSS書いてる感じとだいぶ感覚が違うんですよね。それはこの後書くTailwind CSSの設計思想によるところが大きいからだと思うんですが、何にせよ、こういう風に書くのはナシではないとは思います。BEMっぽく書いてTailwind CSSを使うなら、PostCSSのプラグインをちょいちょい組み合わせると色々快適になります。
CSS Modules
まぁここまでがReactやらVueナシの世界の話でして。
HTMLとCSSだけ書いてると、CSS設計的にはその先に行けないなーというか。筆者的にはその先にあるのがReactだのVueだと思ってるんですよね。これは、ReactとかVueはそういうものだというわけではなく、私はReactやVueを、そういうCSS設計でBEMの先にある存在として扱おうと考えてるってことです。Reactはコンポーネント指向の「ライブラリ」なので、それをそいういう風に扱いたいなーと考えている筆者が居る感じといいますか。まぁこのあたりの話は前回の記事を読んでいただくとして。
ここからは、じゃーReactでどう書く?ってのを、アレやコレやためしたというのを書いていきます。
まずはCSS Modulesです。
まずReactで何かCSS書く時にどうする?って選択肢として挙がるのがCSS Modulesだと思います。GatsbyでもCSS Modulesオススメよって書いてあります。
まぁ筆者的にはもうこれはオススメすべきやつじゃないんじゃないのって感じではあるんですが。
何はともあれ、どんな感じか、例のUIをCSS Modulesで書いてみたコードが以下です。
<CssModulesBlock src="https://placekitten.com/400/300">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</CssModulesBlock>
import styles from './css-modules-block.module.css';
export const CssModulesBlock = ({ children, src }) => (
<div className={styles.container}>
<div className={styles.inner}>
<div className={styles.textWrapper}>
{ children }
</div>
<div className={styles.imgWrapper}>
<img className={styles.img} src={src} alt="" />
</div>
</div>
</div>
);
.textWrapper {
padding: 0 0 20px;
}
.textWrapper > * + * {
padding: 20px 0 0;
}
.img {
display: block;
width: 100%;
}
@media (min-width: 768px) {
.container {
margin: 0 0 0 -40px;
}
.inner {
display: flex;
width: 100%;
}
.textWrapper {
padding: 0 0 0 40px;
width: 50%;
}
.textWrapper > * + * {
padding: 20px 0 0;
}
.imgWrapper {
padding: 0 0 0 40px;
width: 50%;
}
}
ざっくり言うと、CSSをファイルをimportすると、そのCSSファイルの中にあるクラスセレクタに、ランダムな文字を足してくれて、それを各要素のclassNameに指定していくって感じです。headの中にはそれに対応するセレクタが突っ込まれます。こんな感じに。
<style>
.css-modules-block_textWrapper__JEfrZ {
padding: 0 0 20px;
}
.css-modules-block_textWrapper__JEfrZ > * + * {
padding: 20px 0 0;
}
.css-modules-block_img__5jb8q {
display: block;
width: 100%;
}
@media (min-width: 768px) {
.css-modules-block_container__2xV22 {
margin: 0 0 0 -40px;
}
.css-modules-block_inner__1Fwz- {
display: flex;
width: 100%;
}
.css-modules-block_textWrapper__JEfrZ {
padding: 0 0 0 40px;
width: 50%;
}
.css-modules-block_textWrapper__JEfrZ > * + * {
padding: 20px 0 0;
}
.css-modules-block_imgWrapper__JeJhQ {
padding: 0 0 0 40px;
width: 50%;
}
}
</style>
そしてHTMLの方はこうなります。
<div class="css-modules-block_container__2xV22">
<div class="css-modules-block_inner__1Fwz-">
<div class="css-modules-block_textWrapper__JEfrZ">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</div>
<div class="css-modules-block_imgWrapper__JeJhQ">
<img class="css-modules-block_img__5jb8q" src="https://placekitten.com/400/300" alt="">
</div>
</div>
</div>
この結果、ほとんどこのコンポーネント内でスコープを作ったみたいな状態にできます。
なるほど悪く無さそう。それにこれ、なるべくCSSは純粋なCSSとして扱おうとしているアプローチがいい感じに見えますね。さらに、このCSSの中ではさっきBEM + Tailwind CSSでやったみたいに、@apply
が使えたりもします。PostCSSが使えるってことです。CSS in JSとかハックっぽい感じがしますし、そんなことする必要ないでしょ?これでよくない?って思ってました。初めは。
最終的に自分はTailwind CSSとCSS in JSを選ぶんですけど、Tailwind CSSではどうしても書きづらい部分があって、そういう部分はCSS Modulesを使うつもりでいました。どういうやつが書き辛いのかは追って書いていきます。
でも結局CSS Modulesを使うのはやめました。理由としては以下のような感じです。
ファイルが増えてめんどくさい
CSS Modulesを使うと、1コンポーネント毎に1つ、CSSファイルを作らないといけないんですよね。「えー?そんなの大したこと無くない?」「CSSなんだからCSSファイルに書くのは当然でしょ」って最初は思ってたんですが、やってみるとこれ、だいぶゴチャゴチャして書き辛いと感じました。特に、ただ3,4行のCSSを書きたいだけの時にファイルが増えていくのがなかなか辛いなーと。CSS Modulesの対抗馬となるのは各種CSS in JSなんですが、これは対抗馬であるCSS in JSの方がスッキリ読みやすいと思ったというのがまずひとつ。
順番が保証されないとか怒られる
Gatsbyでやってると、別の画面に遷移した時、その画面でコンポーネントの読み込み順が異なると、「CSSの読み込み順が異なるからスタイルが意図したとおりに反映される保証ねーぞ」みたいにwebpackに怒られました。こんな感じです。
これ、すごいややこしいんですが、CSS Modulesって、CSSを追加で読み込むんで、コンポーネントをimportする順番が違ってると、これはまずいんじゃない? CSSの順序違ったら、想定している描画結果変わっちゃわない? っていちいち警告してくれるんですよね。
なんかこれを回避するには、importの順番を揃えるとかせねばならないらしく、いやーめんどくさい……ってなりました。まぁこれは無視しても良かったんですが、とりあえず筆者的には「うーんCSS Modulesじゃないほうがいいのかなー」って思わされた事件でした。
webpack依存技術である
なんか最初はCSS標準技術に近いやつだなって思ってたんですけど、結局の所これ、webpackをゴニョゴニョして無理矢理スコープっぽくしてるだけなんですよね。GatsbyだのNext.jsは、webpackに乗っかっているReactのその上に存在するレイヤーなので、なんかこのwebpackの機能に依存しているCSS Modulesという存在が、フレームワークとうまいこと噛み合ってねーなと感じました。この後書くCSS in JSもほぼほぼ同じことをやっているんですが、JSファイルの中でチョロっと書けてしまって。それに対してCSS Modulesはお行儀よくCSSファイルにしないといけないんですよ。そしてwebpack依存技術だぞっと。
まぁそんなこんなで、端的に言うとCSS in JSのほうがベターであると考え、筆者はCSS Modulesを使うのはやめました。ありがとうさようならCSS Modules。
BEM混ぜます?
CSS Modulesじゃないとするとどうしようかなーと思って、その一つの選択肢が、一部だけBEM的に書く。グローバルな感じのCSSに一部BEMで書いたCSSを書いちゃうという方法です。最初はこれで良いんじゃないかなーと思ってやってたんですが、結局やめました。なぜかと言うと、さっき書いたみたいな、グローバルなCSSがどんどん増えるっていう問題がまずあるんですが、それよりも、
- Reactコンポーネント化したUI
- BEMで組んだUI
が混在することが、管理上辛いなーと感じたためです。
こっちはReactコンポーネントで完結してて、こっちは一部だけグローバルな部分にCSSが書いてあるみたいな状態、純粋に全部BEMで書いてあるよりカオスな感じがしてきたので、この方法はやめました。設計的によろしくない気がします。さようならBEM。Reactの世界ではやはりいらなかった。
CSS in JSどれがいい選手権
そしたら必然的に選択肢として、CSS in JSにするかなー。あんまり乗り気ではないけど……と思うものの、どれを選んだら良いのかわからぬ。そもそもCSS in JSを選ぶこと自体に筆者はしっくり来ておりません。
CSS in JSどうなのよって思うポイントは色々ありました。
まず種類が多すぎるところ。なんかいっぱいあってどれが良いかよくわからんし、いちいち全部調べたくないんですが……というか。そもそもなんかそういう標準技術でもなんでも無いやつにCSSまるっと任せちゃうのが不安ってのもあります。2年後には無くなってそうだなーとか思っちゃいますね。
そして、そもそもJSでCSSを当てるというアプローチ自体どうなんですか?それイケてる実装なんですか?というのが全然しっくり来ない。昔はCSSが利用できない環境でも使えるようにとかあったけど、ああいうのはもう気にしなくていいの? 直にstyle書いちゃうのってなんかダメな理由無かったっけ? とかなんか色々モヤモヤが。
そんなCSS in JSに対するモヤモヤを解消するべく、なんか色々プレゼンを見たりなどしました。(まじめだな)
見た中で結構良かったやつは以下2つです。
この2つは結構わかりやすいかなーと思ったんで、まぁ興味ある人がいたらオススメです。
1つ目は、Styled Componentsを作ってる人のプレゼン、2つめは何かでかいカンファレンスでCSS in JSと素のCSSを比較してpros and consを話してるやつです。
まずStyled Componentsの人のプレゼンの方は、今に至るまでのCSS in JSの歴史みたいなものを紹介していて、今後どうなるかというのが話されてます。ざっくり内容をまとめると、
- 今のCSS in JSライブラリはお互いに真似し合ってて同じ様なAPIがある
- 今後どうなるかはみんなでアイデアを出し合っていこうね
みたいな感じです。なんか個人的にはベストワンが知りたかったので、Styled Componentsがいいんだぜ的なノリを期待していたんですが、ライブラリ作ってる側としてもそうかーまぁ先のことは分からんよねということですね。そして、「どのライブラリも真似し合ってて大体同じなんすよね」みたいなことを言っていて、これには深く納得してしまいました。
この後、CSS in JSのコードを紹介していきますが、たしかにどれも似た感じで書けるんですよね。似てるっていうかほぼ同じAPIです。そしてどれも、基本的なCSSの書き方をそこそこリスペクトしたというか、あんまり飛び抜けたAPIにはしていない感じです。
なので、「JSでCSSを書く」っていうよりかは、「JSの中にフツーに書いたCSSを混ぜられる」って感じになってますね。自分の印象としては。このプレゼンを見た後に自分は色々試すんですが、どれを選んでも対して変わらんなというのを思ってます。
CSS in JSのメリット/デメリット
CSS in JSを使うことのメリット/デメリットについてサラッと書いておきます。
まず1つ言えるのがscopedな感じにスタイルを当てられること。具体的にどういう感じかはこの後の例で紹介しますが、端的に言うと、さっきCSS ModulesがやってみせたことをJSでやる。ただそれだけです。なのでまぁCSS Modulesと同様、コンポーネント内でスコープを作った感じになります。これは当然、コンポーネント中心で設計するには大きなメリットといえます。
コンポーネント化されることで得られるメリットとして他に挙げられるのが、コンポーネントのスタイルが、そのコンポーネントのコードに内包されるというところです。BEMで書くと、100個のBEMブロックがあれば、そのCSSがグローバルな部分に突っ込まれたり、もしくは自分でうまいこと管理せねばならんですが、CSS in JSで書けば、Reactコンポーネントを書いた、そのJSファイルに内包される形にできるので、このコンポーネントが使われる時に初めて読み込ませる感じにしやすいです。シンプルにcode splittingが実現できます。
個人的に書いてみても、この「JSファイルの中にCSS書けちゃう」という点は、結構デカいと感じました。プレゼンの中では、こういう風にCSSを書けるから、ルールに沿わないCSSの書き方がそもそもできなくなるというような事を言ってたりしました。これは自分も大いに納得です。
どーでしょうか。BEMを理解してくれている人がいたとして、その人にBEMで書いてくださいと伝えても、どのCSSファイルにどうやって書くとかは別途考えねばならんのです。でもCSS in JSなら、フツーに書けばそのコンポーネントのJSファイルの中に書くことになります。こういうケースでは、サイト全体でCSSがカオスになるみたいなことが、そもそも起こらないぞってことなんです。この安心感は大きいですよ。
あとはまぁ、Reactはコンポーネントで考えるように指向して作られているので、その設計思想にも合ってるかなーという雰囲気を感じます。
メリットはそんなところで、デメリットとして挙げられるのは、
- 標準技術じゃないので寿命がよくわからん
- 再レンダリングのコストがかかる
- ランタイムでスタイルを当てることになる
- JSで混ざってごっちゃになりがち
とかです。これら、どれも納得ではあるんですが、レンダリング周りについては、Next.jsとかGatsbyとかでSSG/SSRするようなアプローチでは、発生しない問題なんですよね。
SSGとは、乱暴に言うと、SPAの最初のレンダリング結果で作ったHTMLを作って返すという仕組みなので、CSS in JSで書いたコードは、JSに依存しない、ただのHTMLとCSSとしてブラウザは受け取ります。なので、CSS in JSとは言えども、SSGする上では別にJSが有効になっていなければ使えない訳ではないし、管理面のプラスが大きいので、CSS in JSでいいかなーという感じです。
CSS in JSでいいかなーというよりか、さっき書いたようなCSS Modulesの問題がCSS in JSでは発生しないので、どちらを選ぶかと言われればCSS in JSかなって感じです。あとは、JSの処理を気軽に混ぜられるのもいいですね。Sass scriptみたいなのをいちいち書きたくないですし、そんなのもはやいらんでしょという感じです。ありがとうさようならSass。まぁSassは使おうと思えば使えますが、Reactで書く上で、あえてSassを選ぶ理由はほとんど無いんじゃないでしょうか。
goober
「おうおう、じゃあそのCSS in JSとやら、見てやろうやないかい!」
ってことで、gooberというCSS in JSのライブラリ
を使ってさっきのUIを書いたのが以下です。
<GooberBlock src="https://placekitten.com/400/300">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</GooberBlock>
import { css } from "goober";
const styles = {
container: css`
@media (min-width: 768px) {
margin: 0 0 0 -40px;
}
`,
inner: css`
@media (min-width: 768px) {
display: flex;
width: 100%;
}
`,
textWrapper: css`
padding: 0 0 20px;
@media (min-width: 768px) {
padding: 0 0 0 40px;
width: 50%;
}
> * + * {
padding: 20px 0 0;
}
`,
imgWrapper: css`
@media (min-width: 768px) {
padding: 0 0 0 40px;
width: 50%;
}
`,
img: css`
display: block;
width: 100%;
`,
};
export const GooberBlock = ({ children, src }) => (
<div className={styles.container}>
<div className={styles.inner}>
<div className={styles.textWrapper}>{children}</div>
<div className={styles.imgWrapper}>
<img className={styles.img} src={src} alt="" />
</div>
</div>
</div>
);
書いてる内容はCSS Modulesの時と大して変わんないんですが、コンポーネントのJSファイルの中にCSSが入ってる感じになってるのが分かるかと思います。
この結果どういうHTMLができるのかと言うと、こうです。
<div class="go2403310744">
<div class="go3735464671">
<div class="go1875143406">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</div>
<div class="go2277549489"><img class="go2950335646" src="https://placekitten.com/400/300" alt=""></div>
</div>
</div>
goうんたらという謎クラスがgooberが作ってくれるやつで、head要素内に以下が追加されます。
<style id="_goober">
@media (min-width: 768px) {
.go2403310744 {
margin: 0 0 0 -40px;
}
}
@media (min-width: 768px) {
.go3735464671 {
display: flex;
width: 100%;
}
}
.go1875143406 {
padding: 0 0 20px;
}
@media (min-width: 768px) {
.go1875143406 {
padding: 0 0 0 40px;
width: 50%;
}
}
.go1875143406 > * + * {
padding: 20px 0 0;
}
@media (min-width: 768px) {
.go2277549489 {
padding: 0 0 0 40px;
width: 50%;
}
}
.go2950335646 {
display: block;
width: 100%;
}
</style>
なるほどこれでコンポーネント内だけにスタイルを当てることが可能ということですね。gooberはTagged template literalで、生成したclassNameを返してくれるので、それをdivなりに指定すればスタイルを当てられます。
どーでしょう。これなら素でCSS書いてるのとあんまり変わらない感じでかけるし、スコープ化も実現されているのが分かるかと思います。
ちなみにNext.jsとかGatsbyなんかでSSGした場合、今紹介したHTMLとstyle要素がはじめっからHTMLファイル上に書き出された状態になってます。だから、CSS in JSってJS依存技術なんでしょ?って思っている方がいましたら、SSGする状況ではそれは誤解です。ランタイムでスタイルを当てるわけじゃないってことです。まーややこしい話ですが。
Styled ComponentsなAPI
次、またgooberです。
gooberには別のAPIも用意されていて、同じ内容を以下のように書くことができます。
<GooberStyledBlock src="https://placekitten.com/400/300">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</GooberStyledBlock>
import React from "react";
import { styled, setup } from "goober";
setup(React.createElement);
const Container = styled("div")`
@media (min-width: 768px) {
margin: 0 0 0 -40px;
}
`;
const Inner = styled("div")`
@media (min-width: 768px) {
display: flex;
width: 100%;
}
`;
const TextWrapper = styled("div")`
padding: 0 0 20px;
@media (min-width: 768px) {
padding: 0 0 0 40px;
width: 50%;
}
> * + * {
padding: 20px 0 0;
}
`;
const ImgWrapper = styled("div")`
@media (min-width: 768px) {
padding: 0 0 0 40px;
width: 50%;
}
`;
const Img = styled("img")`
display: block;
width: 100%;
`;
export const GooberStyledBlock = ({ children, src }) => (
<Container>
<Inner>
<TextWrapper>{children}</TextWrapper>
<ImgWrapper>
<Img src={src} alt="" />
</ImgWrapper>
</Inner>
</Container>
);
これは、たぶんStyled ComponentsのAPIを真似た書き方だと思います。
一つ前に紹介したやつは、あくまでclassNameを作るというものだったんですが、これはまぁなんというか、どうせ君らはclassNameを指定した要素が欲しいんだろ?ってことで、そこまでをまるっと実装したReactコンポーネントをチョチョイと作ってくれちゃう感じです。
さっき、CSS in JSはお互いに真似しあってるって書いたんですけど、そーなんです。これ、Styled Componentsっていう別の著名なCSS in JSのライブラリで採用されている書き方なんですが、この後紹介するEmotionでも、ほぼ同じ感じで書けるようになっています。
ぐはっ。なんか色々書き方があってCSS in JSめんどくせぇ〜〜
と思うかも知れませんが、まーそうですね……。筆者もそう思います。でも自分で書くぶんには1つ選んで使えばいいだけですし、大差無いので書いていけばすぐ慣れる感じではあると思いますよ。
まぁそんな感じで、CSS in JSでも色々な書き方が用意されてたりしますね。Sassで言えば、scss形式かsass形式かみたいな感じに思ってもらえればいいかなと。
ちなみに筆者はこのStyled Components式の書き方は選びませんでした。基本はTailwind CSSで書いていくことにしたので、classNameを指定してスタイルを当てていくやり方と親和性が薄いと感じたためです。どっちが良いとか悪いとか言う話ではなく、Tailwind CSSと組み合わせるにはイマイチかもなって感じです。
変数とかmixinとかそういうやつ
さて、ここまでで色々と紹介してきたわけですが、SassとかPostCSSで書いてた時に使ってたほら、変数とかmixinとか使いたくなりません?なりますよね?
あーなった。今なったよ。
ということで、gooberの中で、変数やmixin的なふるまいを書いてみたのが以下です。
<GooberThemeBlock src="https://placekitten.com/400/300">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</GooberThemeBlock>
const normalize = (remValue) => {
return remValue.toFixed(4).replace(/\.?0+$/, "");
};
const toRem = (value) => {
const px = parseInt(value);
const rem = px / 16;
return `${normalize(rem)}rem`;
};
export const theme = {
screen: {
lg: `@media (min-width: 768px)`,
},
fontSize: {
sm: `
font-size: ${toRem("14px")};
line-height: 1.8;
`,
base: `
font-size: ${toRem("18px")};
line-height: 1.8;
`,
lg: `
font-size: ${toRem("22px")};
line-height: 1.8;
`,
xl: `
font-size: ${toRem("30px")};
line-height: 1.8;
`,
},
colors: {
red: "#ff0000",
green: "#00ff00",
blue: "#0000ff",
},
spacing: {
xs: toRem("3px"),
sm: toRem("5px"),
md: toRem("10px"),
lg: toRem("20px"),
xl: toRem("40px"),
"2xl": toRem("60px"),
},
};
import { css } from "goober";
import { theme } from "../theme";
const styles = {
container: css`
${theme.screen.lg} {
margin: 0 0 0 -${theme.spacing.xl};
}
`,
inner: css`
${theme.screen.lg} {
display: flex;
width: 100%;
}
`,
textWrapper: css`
padding: 0 0 ${theme.spacing.lg};
${theme.screen.lg} {
padding: 0 0 0 ${theme.spacing.xl};
width: 50%;
}
> * + * {
padding: ${theme.spacing.lg} 0 0;
}
`,
imgWrapper: css`
${theme.screen.lg} {
padding: 0 0 0 ${theme.spacing.xl};
width: 50%;
}
`,
img: css`
display: block;
width: 100%;
`,
};
export const GooberThemeBlock = ({ children, src }) => (
<div className={styles.container}>
<div className={styles.inner}>
<div className={styles.textWrapper}>{children}</div>
<div className={styles.imgWrapper}>
<img className={styles.img} src={src} alt="" />
</div>
</div>
</div>
);
theme.jsは、単純にひとつのobjectをexportしてるだけ。色だの文字サイズだの余白だのをまとめ、その中でpxをremに変換したりもしてるんです。これ、別になんかすごいことをしてるわけじゃないです。gooberではCSSとして評価される文字列を作ればいいので、使いまわしたい文字列をまとめとこうぜってだけですね。
どーですか?結構便利じゃないですか?これ。弊社内で、CSS in JSどう書いてるみたいな話をしてたらこう書いたってのを教えてもらったんですが。結構シンプルでしょう?
しかしこれ、めっちゃ変数だらけになりますよね。
でもこうなるのって必然だと思うんです。サイト内で何度も共通使用される数値なり色なりを共通化するのって。
Tailwind CSSで書くとこう
次に紹介するのはなんと、じゃじゃーん。
やっとのことでTailwind CSSというフレームワークです。
「フレームワーク」です。ここポイントです。
今みたいな共通化は一旦おいておいて、例のUIをTailwind CSSで書くとこうです。
<TailwindBlock src="https://placekitten.com/400/300">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</TailwindBlock>
export const TailwindBlock = ({ children, src }) => (
<div className="lg:ml-[-40px]">
<div className="lg:flex w-full">
<div className="
pb-[20px] space-y-[20px]
lg:pl-[40px] lg:w-1/2
">
{children}
</div>
<div className="lg:pl-[40px] lg:w-1/2">
<img className="block w-full" src={src} alt="" />
</div>
</div>
</div>
);
な、なんて短さ……!CSSはどこいった?って感じなんですが、とりあえず、ここまでであえてスルーしてた、JITモードについて少し触れておきます。JITモードっていうのはこれです。
v2.1にプレビュー版として実装された機能で、ちょっぱやでコンパイルできるやつなんですが、これをオンにすると、pb-[20px]
とか、括弧で囲むと、その値が指定されたCSSルールを柔軟に作ってくれます。これで書けばとりあえずその値で好きにお手軽にスタイルが当てられる感じですよ。Tailwind CSSではArbitrary value supportとか呼んでます。以下に例があります。
このAribitary valueで書くことを想定されているのは、そこだけ取り立ててユニークな値とかですね。たとえば、余白は基本30pxにしてるんだけど、1px分ボーダーがあるので29pxにしたいとか、あとは何かアイコンが飛びてて、その飛び出てる距離がそこだけ固有の34pxとかそういうやつです。
最初の話に戻りますけど、デザインにこだわりがある場合に使えないとか言ってる人のは、この機能をきっと知らないですよね?まぁv2.1ではまだプレビューなんですが、このまま入るのはほとんど間違いないかと思います。とりあえずこれで好きな値でもサクサクかけたりするわけです。
Tailwindで変数とかmixinとかヤツ
さて、ここからが本題なんですが、Tailwind CSSで、さっきgooberのコードで変数なりを使ったやつを再現するとこうなります。
<TailwindThemeBlock src="https://placekitten.com/400/300">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</TailwindThemeBlock>
const normalize = (remValue) => {
return remValue.toFixed(4).replace(/\.?0+$/, "");
};
const toRem = (value) => {
const px = parseInt(value);
const rem = px / 16;
return `${normalize(rem)}rem`;
};
module.exports = {
mode: "jit",
purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
screens: {
lg: "768px",
},
fontSize: {
sm: [toRem("14px"), "1.8"],
base: [toRem("18px"), "1.8"],
lg: [toRem("22px"), "1.8"],
xl: [toRem("30px"), "1.8"],
},
colors: {
red: "#ff0000",
green: "#00ff00",
blue: "#0000ff",
},
spacing: {
xs: toRem("3px"),
sm: toRem("5px"),
md: toRem("10px"),
lg: toRem("20px"),
xl: toRem("40px"),
"2xl": toRem("60px"),
},
},
},
};
export const TailwindThemeBlock = ({ children, src }) => (
<div className="lg:-ml-xl">
<div className="lg:flex w-full">
<div className="
pb-lg space-y-lg
lg:pl-xl lg:w-1/2
">
{children}
</div>
<div className="lg:pl-xl lg:w-1/2">
<img className="block w-full" src={src} alt="" />
</div>
</div>
</div>
);
Tailwind CSSって、コンフィグファイルに色々さっきやってたような変数なりを定義し、それがそのままユーティリティークラスになるって感じなんですよ。
この場合だと例えばpb-lg
とかいうクラスがありますが、これはコンフィグのspacing.lg
として指定されているtoRem("20px")
を参照し、padding-botto: 1.25rem
になるって感じですね。
こんな風に、Tailwind CSSは、さっきみたいな、デザインシステム的なアプローチがbuilt inなんです。というかむしろ、先にそれがあったんですが、そこでなお、ユニークな値もカバーできるようにJITモードみたいのが加わった感じですね。gooberでほとんど同じことしてますよね? だから、どうせこういうことをするならTailwind CSSを選んでおいたら良いんじゃない?って思うのです。
Tailwind CSSは、そもそもCSS in JSとはちょっと毛色が違いまして、ユーティリティークラスを作る「フレームワーク」なんですよ。
An API for your design system
筆者はこのTailwind CSSが好きで、なんで好きなのかっていうところなんですが、それはこの、
- デザインシステムを定義する
- それを使って画面を作っていく
というアプローチをベースにしているからなんですよね。
Tailwind CSSは凝ったデザインに向かないみたいなことをいう人はきっとこうなんでしょう。
「好きにCSSを書くにはいちいちコンフィグをいじらないといけない」
「へんなユーティリティークラスも覚えないといけないし不便なだけでしかない」
「決まった値で作るだけのデザインのこだわりのないサイトなら向いてるんじゃない?」
いやーーちょっとまって〜
お前は何も分かっていない……
Tailwind CSSのことを1mmも分かっていない!
と思うわけなんですよね〜〜〜
Tailwind CSSのアプローチは、基本的に用意したユーティリティークラスで組んでいき、その中で例外的にユニークな値を使うということなんですよね。これは束縛です。トップページに書いてあるんですが。
constraint-based
An API for your design system.
って。これはなかなかいいキャッチコピーだと思いますよ。「制限ベース」「あなたのデザインシステムのAPI」なんですよ。
この制限を「凝ったデザインには向いていない」と感じするその感覚。なんかそれが、あーこの人はデザインに興味ないねって感じてしまいますね〜〜筆者的には。
そんな事言われても現実の仕事では無理じゃん?
そ~いうことを書くとですね、あー意識高い人なのねみたいな風に思われるんですが。
「いやーTakazudoさんのおっしゃりたいことは分かりますよ」
「でも私がやってる仕事ってそういうやつじゃないんですよね〜〜」
って。いやーね、デザインの工程と実装の工程は2つで1つなんですよ。余白が揃ってなかったら揃えるし、色がブレてたらまとめるみたいな役割は考えないんですかね? デザインカンプで1pxずれてたら、それをそのまま愚直にCSSに書いちゃうんですか? それがこだわってるデザインだとでも? こだわりがないのはあんたのCSS設計だろー?って。
というわけで、私がTailwind CSSは凝ったデザインに向かないみたいな呟きを見て「は?何いってんの?」と思ったのはそういう理由です。Tailwind CSSが凝ったデザインに向いていないなんていうことは1mmも無い。むしろ、凝ったデザインをうまく実現することを指向して作られていて、あんたはその事に気付いてすらいない。のだ。
まぁ、私が誰かに実装を頼むとしたらそういうところまで見てCSS設計をして欲しいですね。それはそこそこ難しいことは分かっていて書いてますが。
めちゃくちゃclass長くて読みづらいんだけど
まー私の個人的な意見はそのくらいにしておくとして、ここで2つほど、Tailwind CSSで書くとここが辛いみたいなのを緩和するTips的な話を2つします。
まず1つ目はクラス名について。
Tailwind CSSで書いていると、className長すぎでやってらんないんだけどと思われることはあると思います。こんな感じ。
<button
type="button"
className="relative w-1/2 bg-white border-gray-200 rounded-md shadow-sm py-2 text-sm font-medium text-gray-900 whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:z-10 sm:w-auto sm:px-8"
>
まぁ確かにわからんし嫌になるなというのは分かります。
そういう場合には自分はこうしてます。
<button
type="button"
className="
relative py-2
w-1/2 sm:w-auto sm:px-8
text-gray-900 bg-white
text-sm font-medium whitespace-nowrap
border-gray-200 rounded-md shadow-sm
focus:outline-none
focus:ring-2 focus:ring-indigo-500
focus:z-10
"
>
なんとなく関わりがあるプロパティ毎に纏めとく感じですね。テキスト周り、幅関係、フォーカス時の処理とかそういう感じで。class属性値に改行とかホワイトスペースとか混ざりまくってていいわけ?ってちょっと気になって調べたんですが、仕様的には問題無さそうです。
それと、Netlifyで、Tailwind CSSを使ってリニューアルしたぞって話があって、
その中で紹介されている@netlify/classnames-template-literals
がいい感じです。
以下みたいに書くと、
import ctl from "@netlify/classnames-template-literals";
const Button = ({ active=false }) => {
const buttonClasses = ctl(`
bg-black text-white
p-1 rounded-sm
${active && 'border border-pink-600'}
`);
return <button className={buttonClasses}>Go!</button>;
}
activeがtrueがfalseな時に、buttonClasssesにfalse
だとかundefined
だとかが混ざってきたりしてめんどくさいんですが、このctl
は、それをpurgeして、余分なホワイトスペース/改行を詰めてくれます。Tailwind CSSのオトモにおすすめです。
そもそもややこしいCSSはコンポーネント分割して良いのでは
もひとつ。コンポーネントの書き方についてです。
Tailwind CSSとは直接的に関係無いんですが、こうやってReactコンポーネントでかける環境下に置いて、スモールスクリーン時/ワイドスクリーン時で大きくレイアウトが変わるUIを、mediaquery達人芸みたいな方法で組むのはわかり辛いのでは?というのも考えました。
例えば例のUIは以下のようにも書けます。
<TailwindSeparateBlock src="https://placekitten.com/400/300">
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
<p>The quick brown fox...</p>
</TailwindSeparateBlock>
const VerticalBlock = ({ children, src, className }) => (
<div className={className}>
<div className="pb-lg space-y-lg">{children}</div>
<img className="block w-full" src={src} alt="" />
</div>
);
const HorizontalBlock = ({ children, src, className }) => (
<div className={`-ml-xl ${className}`}>
<div className="flex w-full">
<div className="pb-lg space-y-lg pl-xl w-1/2">{children}</div>
<div className="pl-xl w-1/2">
<img className="block w-full" src={src} alt="" />
</div>
</div>
</div>
);
export const TailwindSeparateBlock = (props) => (
<>
<VerticalBlock className="lg:hidden" {...props} />
<HorizontalBlock className="hidden lg:block" {...props} />
</>
);
CSS Grid Layoutを使えばもっと短く書けるぜ?みたいのはあるとは思うんですけど、単純にスクリーンサイズで大きくレイアウトが変わる場合、mediaqueryを使ってあれやこれやを打ち消したりなどするよりか、バッサリ分割してしまったほうが見やすいかとか思ったんですがどうでしょう。なんかアクセシビリティ上の問題とかあるかな?とか考えたけど思いつかなかったので。
コンポーネントの振る舞いというかインターフェースというか、そういう部分は、こういう風に分割されていようと無かろうと変わらないので、こういう書き方法がわかりやすいかな?と思ったのですがどうでしょう。いやそれはまずいぜみたいな事があれば教えていただけるとありがたいです。
まーこう書いたほうが良いんじゃないというよりかは、こう書いてもアリかな?って思ったのでこの記事の内容として混ぜました。
Tailwind CSSで書き辛いヤツら
さて、まぁそんなわけでTailwind CSSで書くのが良いというか、CSS in JSなどで書いていっても、最終的にはTailwind CSSのコンフィグのようなしくみを自作することになるであろう故、Tailwind CSSを俺は選ぶぜというのが筆者の技術選択なんですが、最初の方でちょっと触れたように、Tailwind CSSではどうにも書き辛いというのがあります。
それは、WYSIWYGで入力されたHTMLや、markdownをコンバートして作られたHTMLを混ぜるときです。
そういうHTMLはこういう感じになるでしょう。
<h2>heading</h2>
<p>paragraph paragraph paragraph...</p>
<h3>heading</h3>
<p>paragraph paragraph paragraph...</p>
<p>paragraph paragraph paragraph...</p>
<ol>
<li>list</li>
<li>list</li>
</ol>
<p>paragraph paragraph paragraph...</p>
ウッ…… classNameが指定できない……
それはそうですね。コンポーネントの中にこういうコードを突っ込む場合、これ、どういうふうにCSSを書きます?フツーに考えれば、子供セレクタとか子孫セレクタでスタイルを当てるんじゃないでしょうか。こんな風に。
.richtext h2 {
font-size: ...;
padding: ...;
}
.richtext h3 {
font-size: ...;
padding: ...;
}
.richtext p {
padding: ...;
}
Tailwind CSSだとこういうのはできないんですよね。残念ながら。どうしよう。
自分はこういう箇所にだけCSS in JSを使うことにしました。さっきのgooberでやったようにです。しかし、そうすると、Tailwind CSSのコンフィグに定義した諸々を活かしづらいです。そこで便利なのがtwin.macroというちょっとしたライブラリです。
これは何かって言うと、CSS in JSの処理の中にTailwind CSSのユーティリティークラスを混ぜられるというモノです。コードを見たほうが速いので、コードを紹介しましょう。こんな感じです。
<WysiwygBlock src="https://placekitten.com/400/300">
<h2>The quick brown fox</h2>
<p>The quick brown fox...</p>
<h3>The quick brown fox</h3>
<p>The quick brown fox...</p>
<ol>
<li>The quick brown fox jumps over the lazy dog. </li>
<li>The quick brown fox jumps over the lazy dog. </li>
<li>The quick brown fox jumps over the lazy dog. </li>
</ol>
<p>The quick brown fox...</p>
<ul>
<li>The quick brown fox jumps over the lazy dog. </li>
<li>The quick brown fox jumps over the lazy dog. </li>
<li>The quick brown fox jumps over the lazy dog. </li>
</ul>
</WysiwygBlock>
今回はWYSIWYGのコードが突っ込まれるという想定で、Reactコンポーネントは、そんな要素らを受け取ると想定します。
以下、children
が突っ込まれるdivをルートとし、Emotionでスタイルを当てます。
import { css } from "@emotion/css";
import tw from "twin.macro";
const richtext = css`
h2 {
${tw`text-xl`}
${tw`pb-md`}
}
h3 {
${tw`text-lg`}
${tw`pb-md pt-md`}
}
p {
${tw`pb-lg`}
}
ul {
${tw`ml-xl list-disc`}
${tw`space-y-sm pb-lg`}
}
ol {
${tw`ml-xl list-decimal`}
${tw`space-y-sm pb-lg`}
}
`;
const Small = ({ children, src, className }) => (
<div className={className}>
<div className={`pb-lg ${richtext}`}>{children}</div>
<img className="block w-full" src={src} alt="" />
</div>
);
const Large = ({ children, src, className }) => (
<div className={`-ml-xl ${className}`}>
<div className="flex w-full">
<div className={`pb-lg pl-xl w-1/2 ${richtext}`}>
{children}
</div>
<div className="pl-xl w-1/2">
<img className="block w-full" src={src} alt="" />
</div>
</div>
</div>
);
export const WysiwygBlock = (props) => (
<>
<Small className="lg:hidden" {...props} />
<Large className="hidden lg:block" {...props} />
</>
);
twin.macroからimportしているtw
というTagged functionが、Tailwind CSSのクラスを評価し、その内容を展開してくれるやつです。これを使うことにより、CSS in JSで書きつつも、その中でTailwind CSSのユーティリティーを使うというマジックが実現できます。
twin.macroは主にStyled ComponentsとEmotionに対応しているので、筆者は@emotion/cssを使うことにしました。
Emotionは、端的に言って、さっきのgooberと同じ感じです。Emotionを使うにも色々なAPIが用意されていて選択肢が色々あるんですが、webpack依存がイヤだしclassName管理に合ってるということで、@emotion/css を選びました。この構成なら、twin.macroはnpm installしてimportするだけで使える気がします。たぶん。
これでTailwind CSSで書きつつ、書き辛いコンポーネントもなんとか打倒することができました。BEMコンポーネントがゼロ、全てのスタイルはそれぞれのコンポーネントの中に閉じ込められました。CSS in JSの役割もclassName作ってくれるだけにとどめました。やった!
ちなみにtwin.macroやさっき紹介したNetlifyのctlでTailwind CSSのクラスを使うと、VSCodeが補完してくれないんですが、以下設定をすればできるのでVSCodeの人はやるといいです。
とりたててユニークなUI
Tailwind CSSで書きづらいなーというのがもう一つ。それは、とりたててユニークな見た目をしているUIです。そのデモを作ってみました。こんな感じです。
なんというか、数値的に具体的に何ピクセルとか何%とか、ここでしか使わない値がボンボン出てきて、className="
って書こうとして手が止まる感じのヤツです。
こういうのは、そう思った時点でもう諦めて最初からCSS in JSで書くと良いよって思って自分でも書いてみたんですが、JITモードオンであれば、そこそこTailwind CSSでも書けますね。こんな感じになりました。
<ComplicatedBlock />
import { css } from "@emotion/css";
const styles = {
gradient1: css`
background: rgb(34, 193, 195);
background: linear-gradient(
0deg,
rgba(34, 193, 195, 1) 0%,
rgba(253, 187, 45, 1) 100%
);
`,
gradient2: css`
background: rgb(63, 94, 251);
background: radial-gradient(
circle,
rgba(63, 94, 251, 1) 0%,
rgba(252, 70, 107, 1) 100%
);
`,
gradient3: css`
background: rgb(238, 174, 202);
background: radial-gradient(
circle,
rgba(238, 174, 202, 1) 0%,
rgba(148, 187, 233, 1) 100%
);
`,
shadow: css`
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px,
rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px,
rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
`,
};
export const ComplicatedBlock = () => (
<div
className="
relative pb-[20px]
mb-[20px] lg:mb-[50px]
"
>
<div
className={`
mx-auto
w-full lg:w-[80%]
h-[200px] lg:h-[300px]
${styles.gradient2} ${styles.shadow}
`}
></div>
<div
className={`
absolute
w-[20%] h-[180px]
left-0 bottom-0
hidden lg:block
${styles.gradient1} ${styles.shadow}
`}
></div>
<div
className={`
absolute
w-[20%] h-[180px]
right-0 bottom-0
hidden lg:block
${styles.gradient3} ${styles.shadow}
`}
></div>
<p
className="
absolute top-[40px] right-0 left-0
text-5xl lg:text-7xl
font-bold text-center
"
>
Drink Bar is
<br />
299 Yen
</p>
</div>
);
自分が書いたやり方としては、基本classNameで指定していって、なんかめんどくさそうなやつはCSS in JSの方でやるという感じです。こういうgradientとかって、以下みたいなサイトで作ったりしませんか?
そういうのはTailwind CSSの土台に乗せると若干めんどくさいので、こんな風に部分的にCSS in JSでclassNameを作るのが良いかなーと思ってます。あとは次に話す、Tailwind CSSのユーティリティークラスとして自分で追加するかが良いかなと思います。
Tailwind CSSをもうちょい突っ込んで使う
ハイ。
そんな感じで、Tailwind CSS8割+2割emotion + twin.macroがいいんではないかなーという結論に今のところはなった自分ですが、そうは言ってもなるべくTailwind CSSで完結させようと、ここまでで触れていないTailwind CSSの機能を試したりしました。それらについても少し触れておきます。
ユーティリティクラスの追加
Tailwind CSSのコンフィグにて、完全オリジナルのユーティリティクラスを追加することが出来ます。以下に書いてあるやつです。
これは、前述した、グラデーションやbox-shadowを共通化する時に使いました。
- スモールスクリーン時にだけ表示させる
- ワイドスクリーン時にだけ表示させる
- 段階的にフォントサイズを大きくさせる
みたいのを作ろうと思ったんですが、ユーティリティを定義する中で、他のユーティリティーを呼ぶやり方がよく分からなかったので断念しました。Tailwind CSSに最初から含まれているユーティリティークラスらは、このAPIを使って書かれている
ので、あんまりこう、ユーティリティ同士を組み合わせるような想定では無いのかな?と感じました。できるかも知れないんですが、複雑だしちょっと面倒そうかなと。なので、グラデやbox-shadowとかの、単独で完結しているものらに使うと良さそうです。
コンポーネントの追加
Tailwind CSSでは、ボタンとかのちっさな粒度のCSSルールのまとまりをコンポーネントとして登録できるようになってるんですが、これも使おうとしましたが結局やめました。
なんかこれは、コンフィグの中に書くんですけど、ここで定義するには、いちいち全部JSのオブジェクト形式にしないといけなくて、その時点で書くのがめんどくさいなーという印象です。そして、ここでも他のユーティリティーを使えるのかよくわからず、たとえ使えたとしても、そこまでしたらもはやそれはReactコンポーネントでやるべきであろうと感じたので、少なくともReactで実装するようなプロジェクトでは、この機能を使うことはなかろうと思われました。
疑似要素
::before
とか::after
とかでなんかデコりたい時、Tailwind CSSだと書きづらいという問題があります。これは一応解決法があります。そういう記法を有効にするプラグインがあって、それを使うというものです。
これを使えば<li className="before:empty-content">...</li>
とかいうノリで書けるらしいんですが、自分はこれはやめて、ただspanを突っ込むことにしました。
今回記事を書くに際し調べたんですが、空っぽのspanを使ってもアクセシビリティ上問題ないみたいです。
筆者的には、疑似要素だろうとspanだろうと大して変わらないのであれば、spanの方が分かり安かろうという判断です。Reactコンポーネントの振る舞い/インターフェースとしては同じだし、疑似要素でデコるのもspanでデコるのも、CSSの表現力を補完するための方法が異なるだけで、どんぐりの背比べかなと思ったため。
子供セレクタ
WYSIWYGのところで、そこだけCSS in JSを使うと書きましたが、> *
をclassName="children:..."
と書けるようになるプラグインがあります。
これは使うかもと思ったんですが、結局使う機会がありませんでした。例えばli
を繰り返すような状況は、大抵Reactコンポーネントの内外でArrayにしてからループするので、わざわざ子供セレクタを使ってもややこしくなるだけで、かといってWYSIWYGのように子供のバリエーションが多いと今度はそれだけでは足りなくなるので、結局使わないで終わりました。
あと、あんまり野良のTailwind CSSプラグインを増やしてしまうと、PostCSSのよくわからんプラグインを増やすみたいで、俺の考えた最強のTailwind感が強くなってよろしくないかと考えました。どうしても子供セレクタを使いたいという時はCSS in JSでやるかなって感じです。
最後に
以上で筆者TakazudoがGatsby上でCSSどうするかを考えた話は終わりです。
Tailwind CSSは、ユーティリティーファーストで画面を実装するためのフレームワークなんで、そのへんを理解して使うと違うんではないでしょうか。
あと、多分特にWeb制作界隈の方は、この記事を読んで、「私はHTMLとCSS書いてるけど使う気機会が無さそうだな」とか思ってしまう方が多いみたいなのですが、いや〜〜自分から何もアクションせず、Tailwind CSSを使うような機会は、この業界でフツーに働いていても、ほとんどの人に訪れないんじゃないかなーと思います。Tailwind CSSがものすごいメジャーなものとならない限り。
この技術を使えば○○が効率的にできる、○○の問題を解決できると考え、自分から動いてアクションした結果、そういう仕事が依頼されたり、高い給与で雇われたり、高品質なWebサイトやWebサービスが完成するだけではないですか? そういうマインドセットがいいんじゃないかなーと筆者は考えております。筆者的にはもう手作りでHTMLを増やしていくのはやめたく、それができるようになったなーって感じてます。
筆者は普段は受託開発をしていてCodeGridというところで記事を書いていたりするので、よろしければよろしくどうぞ。