🤧

TailwindCSSのtableスタイリングのつらさをaddVariantで回避する

2023/06/25に公開

はじめに

「tableのスタイル指定めんどくさい」
TailwindCSSを使ったことがある人ならだれでも一度は思ったことがあるこの問題をTailwindCSS v3.1から利用できるようになったaddVariantを利用して解決してみます。

addVariantの説明については以下からどうぞ
https://tailwindcss.com/docs/plugins#adding-variants

公式のサンプルでは「hocus」というカスタムバリアントが用意されており、hoverとfocusに対して同じスタイル記述が必要になる問題をhocusバリアントで解決しています。

tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  // ...
  plugins: [
    plugin(function({ addVariant }) {
      addVariant('optional', '&:optional')
      addVariant('hocus', ['&:hover', '&:focus'])
      addVariant('inverted-colors', '@media (inverted-colors: inverted)')
    })
  ]
}

重複したスタイル指定をまとめられる。

- <a class="hover:text-decoration focus:text-decoration">Link</a>
+ <a class="hocus:text-decoration">Link</a>

今回はこのaddVariantを利用しtableをシンプルにします。

通常のtableスタイリング方法

th,tdに同じクラス名の付与が必要で非常に冗長です。
table要素がきちんとコンポーネント化されている状態であればth,tdにクラスを指定する必要がない状態にできますが、コンポーネント化を行っていないtableのスタイル制御はコピペで頑張ることになりがち。

<table class="border-collapse border border-gray-500">
    <thead>
      <tr>
        <th class="text-white border border-gray-500 bg-blue-700 p-2">名前</th>
        <th class="text-white border border-gray-500 bg-blue-700 p-2">年齢</th>
        <th class="text-white border border-gray-500 bg-blue-700 p-2">職業</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="border border-gray-500 p-2">山田太郎</td>
        <td class="border border-gray-500 p-2">25</td>
        <td class="border border-gray-500 p-2">ソフトウェア開発者</td>
      </tr>
      <tr>
        <td class="border border-gray-500 p-2">田中花子</td>
        <td class="border border-gray-500 p-2">32</td>
        <td class="border border-gray-500 p-2">営業担当</td>
      </tr>
    </tbody>
  </table>
</div>

addVariantと子結合子を利用するスタイリング方法

上記の問題をaddVariantと子結合子で解決してみます。
子結合子についてはこちらを参照。
https://developer.mozilla.org/ja/docs/Web/CSS/Child_combinator

table要素のソース

<table class="border-collapse border border-gray-500
	      table-cell:border table-cell:border-gray-500 table-cell:p-2
	      table-th:bg-blue-700 table-th:text-white">
    <thead>
      <tr>
        <th>名前</th>
        <th>年齢</th>
        <th>職業</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>山田太郎</td>
        <td>25</td>
        <td>ソフトウェア開発者</td>
      </tr>
      <tr>
        <td>田中花子</td>
        <td>32</td>
        <td>営業担当</td>
      </tr>
    </tbody>
  </table>
</div>

th,td要素からclassがなくなり非常にシンプルになりました。
代わりにtableのclassにtable-celltable-thというバリアントでスタイルを追加しています。

tailwind.config.js

addVariantはtailwind.config.jsで設定を追加します。

tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  // ...
  plugins: [
    plugin(function({ addVariant }) {
      addVariant('table-th', ['&>*>*>th', '&>*>th'])
      addVariant('table-td', ['&>*>*>td', '&>*>td'])
      addVariant('table-cell', ['&>*>*>td', '&>*>td', '&>*>*>th', '&>*>th'])
      addVariant('table-tr', ['&>*>tr', '&>tr'])
      addVariant('table-caption', ['&>caption'])
      addVariant('table-thead', ['&>thead'])
      addVariant('table-tbody', ['&>tbody'])
      addVariant('table-tfoot', ['&>tfoot'])
    })
  ]
}

thead, tbody, tfootの有無の差を解消するために、tr,th,td,cellは2種類の記述を行います。(th,tdについてはtrも必要。)

theadやtrは全称セレクタで単純化します。

  • table>thead>tr>thtable>*>*>th
  • table>tr>thtable>*>th

子結合子を使う理由

子結合子を使わずにtable th {}という子孫結合子で書いたことがある方が多いと思いますが、tableが入れ子になっている場合、入れ子側のtableにもスタイルが反映されてしまうため利用が難しい方法になります。
tableが入れ子になることは少ないですがulなどでは入れ子はよく使われるので、親側でスタイルを指定を行う場合は基本的に子結合子の利用を検討する必要があります。

【要注意】addVariant&子結合子の詳細度問題

tailwindcssを利用する一つの理由に「cssの詳細度問題の回避」がありますが、子結合子を利用している関係上どうしても詳細度問題が発生します。
th,tdからスタイル指定がなくなり非常にシンプルになりますが、ここは割と致命的。

以下のようなtableデザインが必要な場合、一つ目のthのみ個別のスタイル指定が行いたくなりますが、classにbg-****を設定するだけでは子結合子に詳細度が負けるためスタイルが反映されません。

<table class="border-collapse border border-gray-500
	      table-cell:border table-cell:border-gray-500 table-cell:p-2
	      table-th:bg-blue-700 // ★詳細度0.1.1
	      table-th:text-white">
    <thead>
      <tr>
	<!-- ★詳細度0.1.0 (↑の0.1.1に負けるため反映されない) -->
        <th class="bg-pink-700">名前</th>
        <th>年齢</th>
        <th>職業</th>
      </tr>
    </thead>
    ~~~~
  </table>
</div>

現時点での回避策

方法1:important指定

一番簡単です

<table class="border-collapse border border-gray-500
	      table-cell:border table-cell:border-gray-500 table-cell:p-2
	      table-th:bg-blue-700 table-th:text-white">
    <thead>
      <tr>
        <th class="!bg-pink-700">名前</th>
        <th>年齢</th>
        <th>職業</th>
      </tr>
    </thead>
    ~~~~
  </table>
</div>

方法2:詳細度0.2.0化バリアントの作成

どうなんでしょう?

tailwind.config.js
const plugin = require('tailwindcss/plugin')

module.exports = {
  // ...
  plugins: [
    plugin(function({ addVariant }) {
      ~~~~~~
      addVariant('x2', ['&&'])
    })
  ]
}
<table class="border-collapse border border-gray-500
	      table-cell:border table-cell:border-gray-500 table-cell:p-2
	      table-th:bg-blue-700 table-th:text-white">
    <thead>
      <tr>
	<!-- x2:で.bg-pink-700.bg-pink-700になるため詳細度が0.2.0になる -->
        <th class="x2:bg-pink-700">名前</th>
        <th>年齢</th>
        <th>職業</th>
      </tr>
    </thead>
    ~~~~
  </table>
</div>

まとめ

要注意部分は気を付けつつ、使える場面があればぜひ利用を検討してみてください。

Discussion