Tailwind CSS の全クラスの使用回数をカウントしてみた

# はじめに
私が所属する[ユニークビジョン株式会社](https://qiita.com/organizations/uniquevision)ではフロントエンドは **TailwindCSS** を使って開発しています。

https://tailwindcss.com/

# ある日のコードレビュー
私がフロントエンドのコードレビューをしていた時でした。

私（お、なんか見慣れない Tailwind のクラスが使われているな！`max-w-max` ？調べてみるか。）

私（なるほど、確かにこの書き方でも問題なさそうだ。でもちょっと気になるのでコメントしておこう。）

> こんな書き方があるんですね。でもあまり一般的ではない珍しい書き方だと思うので、`flex` に入れて子要素のサイズまで縮ませる方が読みやすいと思います。

私「よし、送信、と。」


　　　～～ しばらくして ～～


レビュイー「レビューありがとうございました。指摘のとおり、どちらの書き方でも動作することは分かりました。」

私「お！よかったです。では変更をお願いします！」

レビュイー「でも、**『珍しいかどうか』は分かりませんでした。**」

レビュイー「フロントエンドを普段あまり触らない私にはどちらも同じくらい『珍しい』と思いました。」

レビュイー「どうやったら **『珍しいかどうか』が分かるんですか？** 」

私「・・・」

私「経験・・・とか？」

# 全クラスの使用回数をカウントしてみる
困りました。**経験こそ全てだ！** みたいなことは絶対に言いたくないので、どのクラスをどれくらい使っているかを調べることにしました。
次のような手順で行いました。

- [こちらのサイト](https://tailwind.build/classes)から Tailwind CSS のクラスリストを抽出する。（抽出結果は[こちら](https://github.com/punkshiraishi/count-tailwind-classes/blob/main/tailwind_class_list)）
- シェルスクリプトで対象のソースコードでの各クラスの出現回数をカウントする。

実際のスクリプトと使い方は以下に置いておきました。

https://github.com/punkshiraishi/count-tailwind-classes

## 対象
今回の分析対象は下記です。

- Tailwind CSS を最も使いこなしているであろう [公式ドキュメント](https://tailwindcss.com/) の [ソースコード](https://github.com/tailwindlabs/tailwindcss.com)
- 我々ユニークビジョン株式会社のフロントエンドのソースコード

ただし、公式ドキュメントには説明のために Tailwind CSS のクラス名が頻出しています。
今回はアプリケーション部分にフォーカスしたかったので、[src/components](https://github.com/tailwindlabs/tailwindcss.com/tree/master/src/components) だけをカウントの対象としました。
（ちなみに説明部分の多くは [src/pages/blog](https://github.com/tailwindlabs/tailwindcss.com/tree/master/src/pages/blog) に置かれていました。）


# 集計結果
## [tailwind.com](https://github.com/tailwindlabs/tailwindcss.com)
|クラス名|出現回数|
|---|---|
|flex|204|
|items-center|106|
|relative|103|
|flex-none|93|
|absolute|79|
|sr-only|75|
|text-sm|68|
|justify-center|60|
|font-medium|53|
|w-full|51|
|hidden|50|

<details><summary>続きを表示</summary><div>

|クラス名|出現回数|
|---|---|
|rounded-full|49|
|font-semibold|47|
|leading-6|45|
|flex-auto|44|
|rounded-lg|39|
|px-4|35|
|overflow-hidden|31|
|bg-white|30|
|mx-auto|26|
|border|26|
|text-xs|25|
|text-white|25|
|border-b|25|
|py-2|23|
|px-3|23|
|z-10|22|
|inline-flex|21|
|bottom-0|21|
|pointer-events-none|20|
|font-mono|20|
|rounded-md|19|
|grid|19|
|max-w-7xl|18|
|inset-0|18|
|top-0|17|
|space-x-4|17|
|left-0|17|
|py-1|16|
|mt-10|16|
|flex-col|16|
|duration-500|16|
|shadow-xl|15|
|px-6|15|
|p-6|15|
|overflow-visible|15|
|h-6|15|
|cursor-pointer|15|
|p-4|14|
|leading-5|14|
|transition|13|
|h-8|13|
|border-t|13|
|uppercase|12|
|text-black|12|
|rounded-xl|12|
|mt-4|12|
|inset-x-0|12|
|flex-wrap|12|
|w-6|11|
|transform|11|
|text-lg|11|
|mt-2|11|
|ml-3|11|
|h-10|11|
|block|11|
|text-base|10|
|space-x-2|10|
|shadow|10|
|opacity-0|10|
|items-baseline|10|
|h-full|10|
|h-5|10|
|border-r|10|
|shadow-sm|9|
|overflow-auto|9|
|mt-6|9|
|items-start|9|
|w-8|8|
|truncate|8|
|top-full|8|
|text-right|8|
|shadow-lg|8|
|px-2|8|
|opacity-100|8|
|object-cover|8|
|ml-2|8|
|justify-end|8|
|h-12|8|
|col-span-2|8|
|sticky|7|
|scale-100|7|
|rounded|7|
|pb-6|7|
|pb-4|7|
|min-h-0|7|
|mb-6|7|
|justify-between|7|
|flex-1|7|
|-mx-px|7|
|-mt-6|7|
|w-auto|6|
|w-5|6|
|transition-colors|6|
|text-pink-500|6|
|text-indigo-600|6|
|text-indigo-500|6|
|text-center|6|
|space-x-3|6|
|select-none|6|
|row-start-1|6|
|rounded-tl|6|
|py-6|6|
|py-3|6|
|pr-4|6|
|pb-1|6|
|p-2|6|
|my-auto|6|
|my-6|6|
|mr-3|6|
|min-w-0|6|
|mb-4|6|
|mb-2|6|
|h-1|6|
|font-bold|6|
|col-start-2|6|
|bg-teal-400|6|
|z-20|5|
|w-4|5|
|w-10|5|
|w-1/2|5|
|text-gray-900|5|
|text-2xl|5|
|right-0|5|
|py-4|5|
|pt-6|5|
|pt-2|5|
|pt-10|5|
|pl-4|5|
|mt-1|5|
|max-w-none|5|
|inset-y-0|5|
|h-px|5|
|h-4|5|
|duration-300|5|
|border-l|5|
|bg-top|5|
|w-64|4|
|w-1|4|
|visible|4|
|tracking-wider|4|
|tracking-tight|4|
|text-3xl|4|
|space-y-6|4|
|scale-0|4|
|ring-inset|4|
|pl-2|4|
|mr-2|4|
|ml-auto|4|
|min-w-full|4|
|md:|4|
|grid-cols-5|4|
|grid-cols-1|4|
|fixed|4|
|border-2|4|
|bg-blue-500|4|
|-mx-4|4|
|-mt-px|4|
|-mb-px|4|
|whitespace-pre|3|
|w-px|3|
|w-48|3|
|w-2/5|3|
|translate-y-0|3|
|transition-transform|3|
|transition-opacity|3|
|space-y-8|3|
|space-y-2|3|
|space-y-1|3|
|space-x-1|3|
|sm:|3|
|shadow-md|3|
|scale-95|3|
|row-end-3|3|
|pt-0|3|
|pr-2|3|
|pointer-events-auto|3|
|pb-8|3|
|pb-2|3|
|p-8|3|
|p-3|3|
|p-0|3|
|mt-3|3|
|mt-16|3|
|ml-4|3|
|lg:|3|
|leading-none|3|
|inline-block|3|
|hover|3|
|grid-cols-2|3|
|gap-8|3|
|gap-6|3|
|from-white|3|
|font-sans|3|
|font-extrabold|3|
|focus|3|
|ease-out|3|
|ease-in|3|
|divide-y|3|
|border-transparent|3|
|bg-current|3|
|bg-black|3|
|appearance-none|3|
|-mx-2|3|
|-ml-2|3|
|z-50|2|
|whitespace-pre-wrap|2|
|w-3|2|
|w-12|2|
|tracking-wide|2|
|text-xl|2|
|text-teal-400|2|
|text-pink-400|2|
|text-gray-700|2|
|text-gray-500|2|
|text-4xl|2|
|space-y-4|2|
|space-y-3|2|
|row-start-2|2|
|row-end-2|2|
|rounded-tr-xl|2|
|rounded-tr|2|
|rounded-tl-xl|2|
|rounded-sm|2|
|py-5|2|
|px-5|2|
|px-1|2|
|pt-5|2|
|pt-4|2|
|pr-3|2|
|pl-6|2|
|pl-10|2|
|pl-0|2|
|p-5|2|
|origin-bottom|2|
|order-1|2|
|opacity-50|2|
|mx-4|2|
|mx-2|2|
|mt-8|2|
|mt-5|2|
|mt-20|2|
|mr-4|2|
|mr-1|2|
|ml-6|2|
|min-h-full|2|
|mb-8|2|
|mb-5|2|
|mb-3|2|
|mb-10|2|
|mb-1|2|
|max-w-3xl|2|
|m-0|2|
|left-full|2|
|leading-4|2|
|inset-px|2|
|h-3|2|
|grid-rows-2|2|
|grid-cols-3|2|
|gap-4|2|
|gap-2|2|
|font-serif|2|
|font-normal|2|
|flex-row-reverse|2|
|duration-200|2|
|duration-100|2|
|delay-500|2|
|container|2|
|col-start-1|2|
|col-span-3|2|
|col-end-4|2|
|bottom-full|2|
|bg-transparent|2|
|bg-repeat-x|2|
|bg-red-500|2|
|bg-no-repeat|2|
|bg-left-bottom|2|
|bg-indigo-600|2|
|bg-indigo-50|2|
|bg-gradient-to-t|2|
|bg-gradient-to-b|2|
|align-top|2|
|-translate-x-1/2|2|
|-my-1|2|
|-mt-2|2|
|-mr-4|2|
|-ml-10|2|
|-bottom-1|2|
|z-40|1|
|xl:|1|
|w-56|1|
|w-40|1|
|w-4/5|1|
|w-32|1|
|w-24|1|
|w-16|1|
|w-1/5|1|
|w-0|1|
|underline|1|
|translate-y-4|1|
|translate-y-2|1|
|transition-all|1|
|tracking-normal|1|
|top-px|1|
|top-1|1|
|text-yellow-400|1|
|text-pink-600|1|
|text-pink-300|1|
|text-left|1|
|text-indigo-400|1|
|text-indigo-300|1|
|text-green-900|1|
|text-gray-400|1|
|text-blue-600|1|
|text-blue-500|1|
|text-blue-300|1|
|space-y-10|1|
|space-y-0|1|
|space-x-8|1|
|space-x-10|1|
|space-x-0|1|
|shadow-2xl|1|
|row-span-2|1|
|row-end-4|1|
|row-end-1|1|
|rounded-t-xl|1|
|rounded-b-xl|1|
|ring-white|1|
|pt-8|1|
|pt-32|1|
|pr-8|1|
|pl-8|1|
|pl-12|1|
|pb-16|1|
|pb-0|1|
|origin-right|1|
|origin-left|1|
|order-3|1|
|order-2|1|
|opacity-25|1|
|my-5|1|
|mx-3|1|
|mt-auto|1|
|mt-12|1|
|mr-8|1|
|ml-1|1|
|mb-16|1|
|mb-12|1|
|max-w-xs|1|
|max-w-xl|1|
|max-w-screen-xl|1|
|max-w-full|1|
|lowercase|1|
|left-1|1|
|italic|1|
|invisible|1|
|inset-y-px|1|
|inline-grid|1|
|inline|1|
|h-auto|1|
|h-24|1|
|h-16|1|
|gap-16|1|
|font-black|1|
|flex-shrink|1|
|flex-col-reverse|1|
|col-start-6|1|
|col-start-3|1|
|col-span-7|1|
|col-span-5|1|
|border-dashed|1|
|border-collapse|1|
|border-black|1|
|border-0|1|
|bg-teal-900|1|
|bg-teal-800|1|
|bg-teal-700|1|
|bg-teal-600|1|
|bg-teal-500|1|
|bg-teal-300|1|
|bg-teal-200|1|
|bg-teal-100|1|
|bg-scroll|1|
|bg-red-400|1|
|bg-pink-50|1|
|bg-local|1|
|bg-green-600|1|
|bg-gray-50|1|
|bg-gray-200|1|
|bg-gradient-to-r|1|
|bg-fixed|1|
|bg-clip-text|1|
|bg-clip-padding|1|
|bg-clip-content|1|
|bg-clip-border|1|
|bg-blue-50|1|
|align-baseline|1|
|-my-2|1|
|-mx-3|1|
|-mt-4|1|
|-mt-10|1|
|-mr-px|1|
|-mr-2|1|
|-mr-1|1|
|-ml-px|1|
|-ml-6|1|
|-ml-4|1|
|-ml-16|1|
|-mb-8|1|
|-mb-3|1|
|-mb-1|1|
|-m-8|1|

（0 件は省略しています。）

</div></details>

## ユニークビジョンのプロダクト

|クラス名|出現回数|
|---|---|
|flex|110|
|w-full|71|
|flex-row|67|
|text-center|47|
|items-center|44|
|w-32|35|
|flex-col|33|
|h-full|30|
|flex-shrink-0|27|
|justify-center|26|
|text-right|24|
|w-64|21|
|font-bold|20|

<details><summary>続きを表示</summary><div>

|クラス名|出現回数|
|---|---|
|justify-between|18|
|space-y-3|17|
|flex-grow|17|
|w-48|15|
|space-x-3|13|
|p-10|11|
|w-24|10|
|min-h-0|10|
|text-white|8|
|p-5|8|
|space-x-5|7|
|p-3|7|
|cursor-pointer|7|
|bg-white|7|
|text-lg|6|
|p-16|6|
|whitespace-pre-wrap|5|
|w-56|5|
|space-y-2|5|
|rounded|5|
|px-3|5|
|justify-end|5|
|contents|5|
|break-words|5|
|w-20|4|
|w-2/3|4|
|w-12|4|
|text-sm|4|
|space-y-5|4|
|space-x-1|4|
|relative|4|
|py-1|4|
|px-5|4|
|mb-2|4|
|block|4|
|whitespace-no-wrap|3|
|text-xl|3|
|text-3xl|3|
|space-y-6|3|
|space-x-2|3|
|mx-5|3|
|items-start|3|
|border|3|
|w-40|2|
|top-0|2|
|static|2|
|space-y-1|2|
|shadow|2|
|self-end|2|
|self-center|2|
|rounded-full|2|
|py-5|2|
|py-2|2|
|px-2|2|
|pl-2|2|
|p-2|2|
|overflow-auto|2|
|min-w-0|2|
|mb-5|2|
|leading-none|2|
|h-56|2|
|flex-wrap|2|
|divide-y-2|2|
|break-all|2|
|border-white|2|
|absolute|2|
|w-4/5|1|
|w-4|1|
|w-16|1|
|w-10|1|
|w-1/3|1|
|transform|1|
|text-xs|1|
|text-left|1|
|sticky|1|
|space-y-10|1|
|space-x-4|1|
|space-x-10|1|
|self-start|1|
|select-none|1|
|rounded-r-none|1|
|rounded-r|1|
|rounded-lg|1|
|rounded-l-none|1|
|right-0|1|
|py-3|1|
|px-4|1|
|pointer-events-none|1|
|pointer-events-auto|1|
|p-px|1|
|p-1|1|
|overflow-y-scroll|1|
|overflow-y-auto|1|
|mr-2|1|
|max-w-full|1|
|m-auto|1|
|inset-0|1|
|inline-flex|1|
|h-4|1|
|h-12|1|
|fixed|1|
|box-content|1|
|border-t-0|1|
|border-t|1|
|border-r-0|1|
|border-opacity-50|1|
|border-l|1|
|border-b-0|1|
|border-b|1|
|border-2|1|

（0 件は省略しています。）

</div></details>

# 分析
## 概ね予想通りの結果
tailwindcss.com での出現回数上位のクラスはほとんどは我々が良く使っているものでした。
すなわち、我々の TailwindCSS の使用方法は **Tailwind CSS 公式の使用方法から大きく逸脱していない**ということが分かりました。
そして、冒頭に登場した `max-w-max` は 0 という結果だったので、「珍しい」という直感は間違っていなかったようです。

## tailwindcss.com の方で出現回数が多かったクラス
一方、少なからず見慣れないクラス名もいくつか上位に入っていました。

### `sr-only`
screen reader only という言葉の略称のようです。画面には表示しませんが、音声読み上げ機能などでは読み上げられる、アクセシビリティのためのスタイルとのことです。

https://tailwindcss.com/docs/screen-readers

### `hidden`
`hidden` は CSS の `display: none;` です。
tailwindcss.com では、下記のようにレスポンシブ対応のために使われていることが多かったです。


```html
<div class="hidden md:block">...</div>
```

ユニークビジョンのプロダクトは PC ブラウザ向けのアプリケーションなので、あまり使っていません。

### `flex-none`
`flex-none` は CSS の `flex: none;` です。子要素を伸縮させず、子要素自身の大きさを優先するプロパティです。
後述しますが、我々は代わりに `flex-shrink-0` を多用しているので、使う機会があまりなかったようです。

https://tailwindcss.com/docs/flex#none


## 我々のプロダクトの方が出現回数が多かったクラス
逆に、我々のプロダクトでは多用していたのに、tailwindcss.com ではほとんど使われていなかった例もありました。

### `flex-row`
`flex-row` は CSS の `flex-direction: row;` です。なんと tailwindcss.com では 0 回でした。
`flex` のデフォルトは `flex-row` なので、書く必要が無いためだと思われます。
我々は、**`flex` が `row` か `col` なのかを明示的に書くという方針** だったので、`flex flex-row` のような書き方をよくしていました。
結果、この差が生まれてしまったようです。

https://tailwindcss.com/docs/flex-direction#row

### `flex-shrink-0`
`flex-shrink-0` は CSS の `flex-shrink: 0;` です。flex コンテナ内の要素を縮ませないようにするためのプロパティです。前述したとおり、`flex-none` でも同等の効果を得られます。`flex-shrink-0` は flex の子要素一つ一つに付けるのに対し、`flex-none` は親要素だけに付与すれば OK です。場合によって使い分けることで記述量を減らせそうです。

https://tailwindcss.com/docs/flex-shrink#don-t-shrink

# まとめ
CSS は同じスタイルでも、様々な表現方法があります。余計なプロパティを書いても表示結果が変わらないことが多いです。
プロジェクトが小さいうちは良いのですが、大きくなるにつれ、複雑な CSS がスタイルバグを生み出したり、コードを読む速度を低下させたりするなど、生産性の低下を招きかねません。

CSS をシンプルに保つためには、可能な限り珍しくない（＝よく使われている）ものを使う方がベターです。

今回、Tailwind CSS の全クラスの出現回数をカウントすることで、 **使おうとしているクラスが「珍しいかどうか」を知ることができるようになりました。**

珍しい Tailwind CSS のクラスを使おうとしているときには、代替手段を探してみることをおすすめします。
代替手段が見つからない場合は、そのクラスを使いましょう。

また、一般的な使い方と自分たちの使い方を比較することで、**新しいテクニックやアンチパターンを発見できることが分かりました。**
あなたのプロジェクトでも是非試してみてください。

# 最後に
フロントエンド周りのことをよく呟いています。よければフォローお願いします。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ユニークビジョンという会社のエンジニアです。<br>フロントエンド開発と品質向上に取り組んでいるので、そのあたり興味ある方はフォローしていただけると嬉しいです。<br><br>品質の高いサービスを提供し続ける会社であるために／品質向上チーム <a href="https://t.co/vI7YsKL6QW">https://t.co/vI7YsKL6QW</a></p>&mdash; シライシ@ユニークビジョン (@punksy2) <a href="https://twitter.com/punksy2/status/1576934123670630404?ref_src=twsrc%5Etfw">October 3, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>