🚑

コンポーネント指向時代のCSS: Tailwind CSSを選んだきっかけ

2024/12/05に公開

はじめに

私は普段Tailwind CSSを愛用していますが、そもそもの導入のきっかけの話になります。
結論から言ってしまうと「コンポーネント指向で設計していると、いつの間にかTailwind CSSの車輪の再発明をしてしまっていたから」です。

どういうことなのか、サンプルコードを交えてご紹介します。

コンポーネント指向とPRECSS

前提として「CSS設計完全ガイド」という書籍の影響で、当時はPRECSSというCSSの設計思想を利用していました。

まずは、このPRECSSを利用してコンポーネントを作ってみます。

https://precss.io/ja/

CSSモジュールの分割

まず、シンプルなカードモジュールを作ります。

style.css
.bl_card {
  width: 100%;
  background-color: #fff;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
}

コンポーネントの分割

つぎにカードコンポーネントにスタイルを適用させます。

<div class="bl_card">
  <!-- slot -->
</div>

このようにシンプルな構成であれば、モジュール分割とコンポーネント分割の親和性は悪くないように見えます。

再利用性とHelperClass

つぎに、Cardコンポーネントに背景色をつけたいという要望があったとしましょう。
今回はデフォルトの白に加えて赤と青を追加するとします。

PRECSSとコンポーネントの分割方法の違い

もし、これをPRECSSで実現する場合は、モジュールを分割して実現します。
以下のような3つのモジュールを用意して、それぞれbackground-colorの値だけを変えるという方法をとります。

  • .bl_card
  • .bl_cardRed
  • .bl_cardBlue

しかし、コンポーネントではわざわざ別のコンポーネントを用意するのではなく、Propsから取得した値によって背景色を変更できるようにします。

HelperClassの作成

PRECSSにはHelper Classという考え方があります。
モジュールから分割して1つのスタイルのみを担当する補助的なクラスです。

.hp_bgColor_white { background-color: #fff; }
.hp_bgColor_red { background-color: #ff0000; }
.hp_bgColor_blue { background-color: #0000ff; }

このクラスを切り替えることでコンポーネントの色を変える仕組みにします。

スタイル取得のUtility関数

コンポーネントからHelper Classを取得する関数も作っておきます。

utils/useHelperStyles.ts
export type Color = 'white' | 'red' | 'blue'

export const useHelperStyles = () => {
  const getBgColor = (color: Color) => {
    switch(color) {
      case 'white':
        return 'hp_bgColor_white'
      case 'red':
        return 'hp_bgColor_red'
      case 'blue':
        return 'hp_bgColor_blue'
      default:
        return 'hp_bgColor_white'
    }
  }
}

コンポーネントのPropsでスタイルの変更

では、制作したHelper ClassとUtility関数を利用してコンポーネントを作成してみましょう。
ReactとVue.jsの2パターン紹介します。

Reactの場合

import React from 'react';
import { useHelperStyles, Color } from 'utils/useHelperStyles';

interface CardProps {
  color: Color;
  children?: React.ReactNode;
}

const Card: React.FC<CardProps> = ({ color, children }) => {
  const { getBgColor } = useHelperStyles();

  return (
    <div className={`bl_card ${getBgColor(color)}`}>
      {children}
    </div>
  );
};

export default Card;

Vue.jsの場合

<script setup lang="ts">
import { useHelperStyles, Color } from 'utils/useHelperStyles'

defineProps<{
  color: Color
}>()

const { getBgColor } = useHelperStyles()
</script>

<template>
  <div class="bl_card" :class="getBgColor(color)">
    <slot />
  </div>
</template>

TailwindCSSの再発明

HelperClassの増殖

ここまでで紹介してきたHelper Classで見た目を切り替えるというのは非常に便利です。
しかし、便利さゆえにこれを多様したくなってきます。
例えば文字の配置もこのように変更したいと考えた場合、新たにhp_text_left hp_text_center hp_text_rightのようなクラスを新設することが考えられます。
このような繰り返しでHelper Classが無限に増えていくことが予想されました。

命名規則で悩んだり、Class・Typesの設置の時間を考えると、単純作業の割に時間がかかり過ぎてしまいます。

状態変化の対応にもHelperClass

また、近年のUIは状態に合わせてスタイルが変化していくことが考えられます。
例えば、公開状態ではデフォルトの色で、非公開時は色が薄くなるなどの対応で、これもHelper Classを利用して動的に対応することが考えられます。

このように、Helper Classはありとあらゆるスタイルに対して必要になります。

それならTailwindCSSでいいじゃん

作業の途中で薄々感じてはいましたが、このHelper Classを多様するというのは、結局Tailwind CSSを利用するのと同じことになってしまっていました。
自作のHelper ClassとTailwindのUtility Classはほぼ同義で、劣化版車輪の再発明に無駄な時間をとられてしまっていたのです。
また、今後のCSSの仕様変更への対応なども考えると、保守運用の点で技術的負債となる懸念もあります。

こういった経緯で私はTailwind CSSの導入を決めたのでした。

さいごに

私がTailwind CSSにたどりつくまでを簡単に説明させていただきました。
コンポーネント指向でUIをつくっていくことが主流になった現在、CSSの命名の問題は以前とは異なる考え方になりました。
また状態や状況に合わせてUIのスタイルは以前より細かく変化し、それに対応していかなければなりません。
そうなってくると、どうしても「スタイルをモジュールとしてまとめることの意味が薄くなっている」といえるのではないでしょうか。

Discussion