【Tailwind CSS】@applyでスタイルをまとめてみる

はじめに

この記事はこういう方にオススメ

Tailwind CSSでスタイリングしていて大量のクラスの羅列に「ウッ!」となったことのある方

はじめに

Tailwind CSS、便利ですね。初めて目にしたときには多大なカルチャーショックを受けたものですが、HTMLから離れずにスタイリングができる点、CSSファイルが荒れない点はとても便利だなあと感じます。

独特のクラス名も覚えてきてサクサクスタイリングができるようになってきたなぁ……という今日この頃なのですが、同じクラスのツブツブ達を色んな画面にペタペタとコピペしていて思いました。

<button class="rounded-full bg-gradient-to-tr from-orange-300 to-red-600 px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all">
  ボタン
</button>

長い!読みにくい!!これ、まとめられへんのか!?」と。

@applyとの出会い

まとめられるらしい

@applyを使うと、まとめられるらしい。やってみよ。

お題にこんなボタンを作ってみました。

<div class="p-10">
    <div class="mb-10 flex flex-row items-start gap-10">
    <button
        class="rounded-full bg-gradient-to-tr from-orange-300 to-red-600 px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all"
    >
        ボタン大
    </button>
    <button
        class="rounded-full bg-gradient-to-tr from-green-300 to-blue-600 px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all"
    >
        ボタン大
    </button>
    <button
        class="rounded-full bg-gradient-to-tr from-lime-300 to-teal-600 px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all"
    >
        ボタン大
    </button>
    <button
        class="rounded-full bg-gradient-to-tr from-gray-300 to-gray-200 px-6 py-2 text-base font-bold text-white"
    disable>
        非活性
    </button>
    </div>
    <div class="flex flex-row items-start gap-10">
    <button
        class="rounded-lg bg-gradient-to-tr from-orange-300 to-red-600 px-4 py-2 text-xs font-medium text-white drop-shadow-sm hover:opacity-80 hover:transition-all"
    >
        ボタン小
    </button>
    <button
        class="rounded-md bg-gradient-to-tr from-green-300 to-blue-600 px-4 py-2 text-xs text-white hover:opacity-80 hover:transition-all"
    >
        ボタン小
    </button>
    <button
        class="rounded-md bg-gradient-to-tr from-lime-300 to-teal-600 px-4 py-2 text-xs text-white hover:opacity-80 hover:transition-all"
    >
        ボタン小
    </button>
    <button
        class="rounded-md bg-gradient-to-tr from-gray-300 to-gray-200 px-4 py-2 text-xs text-white"
    disable>
        非活性
    </button>
  </div>
</div>

下準備

この記事執筆にあたってはTailwind CSS v.3.2.2で動作確認を行いました。

また、Tailwind CSSクラスの順序はTailwindCSS公式Prettierプラグインでソートしています。

@applyはメインのCSSファイルに記載していきます。インストールガイドに従うと、最初はこんな感じで、ディレクティブを記載しているはず。

@tailwind base;
@tailwind components;
@tailwind utilities;

@layerとディレクティブそれぞれ

@apply を追加するには、@layerで所属するディレクティブを指定する必要があります。ドキュメントによると、おおむね下記の分類で@layerを指定すると良さそうです。

base

html要素やbody要素に指定する、ルールのリセットやデフォルトスタイル。

components

レイアウト用のコンテナや、ボタンやカードといったコンポーネント単位のスタイル。

utilities

小さな単一目的のスタイル。サイズやカラー等の細かなスタイル。

いよいよまとめていく

@applyしていきます。(この記事ではボタンだけを対象にします)

おさらいすると、ボタンと最初のHTMLはこんな感じです。ボタン大・小と3色+非活性のパターンがあります。

<div class="p-10">
    <div class="mb-10 flex flex-row items-start gap-10">
    <button
        class="rounded-full bg-gradient-to-tr from-orange-300 to-red-600 px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all"
    >
        ボタン大
    </button>
    <button
        class="rounded-full bg-gradient-to-tr from-green-300 to-blue-600 px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all"
    >
        ボタン大
    </button>
    <button
        class="rounded-full bg-gradient-to-tr from-lime-300 to-teal-600 px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all"
    >
        ボタン大
    </button>
    <button
        class="rounded-full bg-gradient-to-tr from-gray-300 to-gray-200 px-6 py-2 text-base font-bold text-white"
    disable>
        非活性
    </button>
    </div>
    <div class="flex flex-row items-start gap-10">
    <button
        class="rounded-lg bg-gradient-to-tr from-orange-300 to-red-600 px-4 py-2 text-xs font-medium text-white drop-shadow-sm hover:opacity-80 hover:transition-all"
    >
        ボタン小
    </button>
    <button
        class="rounded-md bg-gradient-to-tr from-green-300 to-blue-600 px-4 py-2 text-xs text-white hover:opacity-80 hover:transition-all"
    >
        ボタン小
    </button>
    <button
        class="rounded-md bg-gradient-to-tr from-lime-300 to-teal-600 px-4 py-2 text-xs text-white hover:opacity-80 hover:transition-all"
    >
        ボタン小
    </button>
    <button
        class="rounded-md bg-gradient-to-tr from-gray-300 to-gray-200 px-4 py-2 text-xs text-white"
    disable>
        非活性
    </button>
  </div>
</div>
</div>

共通化を考えていく

スタイルに規則性があるので、共通化できるクラスのグルーピングを考えていきます。コンポーネントとしての「ボタン」と、パターンとしての「カラー」は分けて考えることができそうです。

ボタン

まずはボタンの基礎部分を大・小でまとめていきます。形状やホバー効果、テキストカラーの白はボタン共通として良さそうです。@layerは、ボタンはコンポーネントであると言えるのでcomponentsにします。

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-big {
    @apply rounded-full  px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all;
  }
  .btn-small {
    @apply rounded-lg px-4 py-2 text-xs font-medium text-white drop-shadow-sm hover:opacity-80 hover:transition-all;
  }
}

@applyしてできたクラスを適用するとこんな感じ。この時点でもかなりスッキリしました。

<!-- おおきいボタン -->
<button class="btn-big bg-gradient-to-tr from-orange-300 to-red-600">
    ボタン大
</button>


<!-- ちいさいボタン -->
<button class="btn-small bg-gradient-to-tr from-orange-300 to-red-600">
    ボタン小
</button>

カラー

次に「カラー」をまとめてみます。

グラデーション方向を指定するbg-gradient-to-trはボタン共通で使用していますが、あくまで「塗り表現」の一部と考えられるので、カラー側の共通クラスに分類します。@layerは、カラーは細かなプロパティと言えるのでutilitiesにします。

@tailwind base;
@tailwind components;
@tailwind utilities;

~略...~

@layer utilities {
  .red {
    @apply bg-gradient-to-tr from-orange-300 to-red-600;
  }
  .blue {
    @apply bg-gradient-to-tr from-green-300 to-blue-600;
  }
  .green {
    @apply bg-gradient-to-tr from-lime-300 to-teal-600;
  }
}

@applyしてできたクラスを適用するとこんな感じ。スッキリ直感的になりました。

<!-- おおきいボタン -->
<button class="btn-big red">
    ボタン大
</button>


<!-- ちいさいボタン -->
<button class="btn-small red">
    ボタン小
</button>

非活性ボタン

残るは非活性のスタイルなのですが、これはボタンの基礎部分もカラーも共通要素からも少し外れたイレギュラーなスタイルです。「グレーで、影がない」というカラーパターンでも表現できなくはないですが、今回は独立させてみます。

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-big {
    @apply rounded-full px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all;
  }
  .btn-big-disable { /* 追加 */
    @apply rounded-full bg-gradient-to-tr from-gray-300 to-gray-200 px-6 py-2 text-base font-bold text-white;
  }
  .btn-small {
    @apply rounded-lg px-4 py-2 text-xs font-medium text-white drop-shadow-sm hover:opacity-80 hover:transition-all;
  }
  .btn-small-disable { /* 追加 */
    @apply rounded-md bg-gradient-to-tr from-gray-300 to-gray-200 px-4 py-2 text-xs text-white;
  }
}

見たらわかる感じに。

<!-- おおきい非活性ボタン -->
<button class="btn-big-disable" disable>非活性</button>


<!-- ちいさい非活性ボタン -->
<button class="btn-small-disable" disable>非活性</button>

ここまで@applyしてみた完成系

できた!!

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-big {
    @apply rounded-full  px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all;
  }
  .btn-big-disable {
    @apply rounded-full bg-gradient-to-tr from-gray-300 to-gray-200 px-6 py-2 text-base font-bold text-white;
  }
  .btn-small {
    @apply rounded-lg px-4 py-2 text-xs font-medium text-white drop-shadow-sm hover:opacity-80 hover:transition-all;
  }
  .btn-small-disable {
    @apply rounded-md bg-gradient-to-tr from-gray-300 to-gray-200 px-4 py-2 text-xs text-white;
  }
}

@layer utilities {
  .red {
    @apply bg-gradient-to-tr from-orange-300 to-red-600;
  }
  .blue {
    @apply bg-gradient-to-tr from-green-300 to-blue-600;
  }
  .green {
    @apply bg-gradient-to-tr from-lime-300 to-teal-600;
  }
}

<div class="p-10">
  <div class="mb-10 flex flex-row items-start gap-10">
    <button class="btn-big red">ボタン大</button>
    <button class="btn-big blue">ボタン大</button>
    <button class="btn-big green">ボタン大</button>
    <button class="btn-big-disable" disable>非活性</button>
  </div>
  <div class="flex flex-row items-start gap-10">
    <button class="btn-small red">ボタン小</button>
    <button class="btn-small blue">ボタン小</button>
    <button class="btn-small green">ボタン小</button>
    <button class="btn-small-disable" disable>非活性</button>
  </div>
</div>

めちゃくちゃスッキリ!!!

ん? でも待てよ…?

待てよ?

でもこれって、当初Tailwind CSSに感じていた素晴らしさHTMLから離れずにスタイリングができる」「CSSファイルが荒れないに反しているんじゃないでしょうか……?

結局、昔とやっていることが同じじゃない?

コンポーネント指向とTailwind CSS

コンポーネント指向と相性がいいTailwind CSS

Tailwind CSSは、ReactやVue.jsといったフロントエンドのフレームワークの考え方「コンポーネント指向」と相性が良いのです。今回の話題で言うと、コンポーネントの外から中身のコードを気にする必要がなくなる点がGoodです。

なので、たとえばReactであれば・・・

こんな感じのコンポーネントを作って

type ButtonBigProps = {
  caption: string
  color: color
  disabled: boolean
}

const color = {
  red: 'bg-gradient-to-tr from-orange-300 to-red-600',
  blue: 'bg-gradient-to-tr from-green-300 to-blue-600',
  green: 'bg-gradient-to-tr from-lime-300 to-teal-600',
} as const
type color = typeof color[keyof typeof color]

const ButtonBig: React.FC<ButtonBigProps> = ({ caption, color, disabled }) => {
  if (disabled) {
    return (
      <button
        className='rounded-full bg-gradient-to-tr from-gray-300 to-gray-200 px-6 py-2 text-base font-bold text-white'
        disabled>
        {caption}
      </button>
    )
  } else {
    return (
      <button
        className={`${color} rounded-full  px-6 py-2 text-base font-bold text-white drop-shadow-lg hover:scale-95 hover:opacity-70 hover:transition-all`}>
        {caption}
      </button>
    )
  }
}

こんな感じで呼び出せば

<ButtonBig caption='ボタン' color={color.red} disabled={false} />

ボタンコンポーネントを配置している画面側からは直感的で読みやすいコードになるので、同じボタンを色んな画面に配置して、同じクラスのツブツブ達を色んな画面にコピペしていた悩みは消え去ります。

まとめ

@applyは多用するとTailwind CSS本来のメリットを損なってしまうため、ケースバイケースで使っていくことが必要であると感じました。可読性をあまり気にしない場合にはプレーンなTailwind CSS記法のまま使えばよく、どうしても気になる場合に用途を絞って使っていくことが必要ですね。

参考文献