【Tailwind Variants】React×Tailwind CSSで動的スタイリングを実現するTailwind Variantsの使い方

## はじめに
Tailwind Variants は、Tailwind CSSを使用して動的なスタイリングを簡潔に管理できるツールやライブラリです。

一つのコンポーネントに対して、引数を渡して動的にクラス設定が可能になります。
Webアプリケーションの開発において、UIコンポーネントのスタイリングは重要な要素です。Tailwind CSSを使うと、直感的にスタイルを設定できますが、次第にコードが冗長化したり、似たようなスタイルを持つ要素の管理が複雑化する問題が発生します。

特に、状況に応じてスタイルを動的に変更したい場合、Tailwind CSS単体では対応が煩雑になることがあります。このような課題に対して、Tailwind Variants は、動的スタイリングを簡潔に記述できる方法を提供します。

### 動的スタイリングがない場合の課題
例えば以下の様にサイト内でボタンがある場合、
単純にTailwind CSSのスタイルを定義すると、ボタンごとに異なるクラスを個別に設定する必要があります。
この場合、コンポーネント化を進めても、例えば複数のボタンコンポーネントを用意してそれぞれに異なる色を設定する必要が出てきます。.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3970579/fc25febd-d710-b95b-26c9-4954c275f366.png)


   実装例
   ```html
   <!DOCTYPE html>
   …
        <div className="grid grid-cols-3 items-center gap-4 font-bold text-lg">
          <h2 className="text-center">Size</h2>
          <h2 className="text-center">Primary</h2>
          <h2 className="text-center">Secondary</h2>
        </div>
        <div className="grid grid-cols-3 items-center gap-4 text-center">
          <h2 className="text-lg font-semibold">Large</h2>
          <button className="bg-blue-500 text-white py-2 rounded w-40">Button</button>
          <button className="bg-white text-black border border-black py-2 rounded w-40">Button</button>
        </div>
        <div className="grid grid-cols-3 items-center gap-4 text-center">
          <h2 className="text-lg font-semibold">Medium</h2>
          <button className="bg-blue-500 text-white py-2 rounded w-32">Button</button>
          <button className="bg-white text-black border border-black py-2 rounded w-32">Button</button>
        </div>
        <div className="grid grid-cols-3 items-center gap-4 text-center">
          <h2 className="text-lg font-semibold">Small</h2>
          <button className="bg-blue-500 text-white py-2 rounded w-24">Button</button>
          <button className="bg-white text-black border border-black py-2 rounded w-24">Button</button>
        </div>
   …
   ```
   <br>

これに対し、Tailwind Variantsを使うと、色や状態に応じて動的にクラスを設定でき、再利用可能なコードを効率よく管理できます。

## 事前準備

ここでは、Tailwind Variantsを導入するために必要なReactとTypeScriptのセットアップを行います。  
もしすでにReactとTypeScriptの環境が整っている場合や、内容だけ知りたい場合は、次の「[Tailwind Variantsの導入](#tailwind-variantsの導入)」に進んでください。  
実際にセットアップを行いたい場合は、この手順を順番に実行して、Tailwind Variantsを使うための準備を整えてください。

### 各種インストール

1. **Reactと型宣言をインストール**

   ```bash
   npm install react react-dom
   npm install --save-dev @types/react @types/react-dom
   ```
   <br>

2. **TypeScriptのインストール**

   ```bash
   npm install --save-dev typescript
   ```
   <br>

3. **Viteのインストール**

   ```bash
   npm install --save-dev vite
   ```
   <br>

4. **Tailwind CSSのインストール**

   ```bash
   npm install -D tailwindcss
   npx tailwindcss init
   ```
   <br>

5. **Viteのインストール**

   ```bash
   npm install --save-dev vite
   ```
   <br>

### 各種作成

1. **TypeScriptの初期設定**

   TypeScriptの設定ファイル`tsconfig.json`を初期化します。

   ```bash
   npx tsc --init
   ```
   <br>

2. **tsconfig.jsonを編集**

   `tsconfig.json`ファイルを以下のように編集します。

   ```json
   {
     "compilerOptions": {
       "target": "ESNext", 
       "module": "ESNext", 
       "jsx": "react-jsx", 
       "strict": true, 
       "moduleResolution": "node", 
       "esModuleInterop": true, 
       "skipLibCheck": true, 
       "forceConsistentCasingInFileNames": true 
     },
     "include": ["src/**/*"], 
     "exclude": ["node_modules"] 
   }
   ```
   <br>

3. **index.htmlの作成**
   index.html
   ```html
   <!DOCTYPE html>
   <html lang="ja">
     <head>
       <meta charset="UTF-8" />
       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
       <title>Vite App</title>
     </head>
     <body>
       <div id="root"></div>
       <script type="module" src="/src/tsx/index.tsx"></script>
     </body>
   </html>
   ```
   <br>

4. **Reactファイルの作成**
   src/tsx/index.tsx
   ```tsx
   import React from 'react';
   import ReactDOM from 'react-dom/client';
   import App from './app';
   import '../css/output.css';

   const rootElement = document.getElementById('root');
   if (!rootElement) {
     throw new Error('Root element not found. Check your index.html file.');
   }

   const root = ReactDOM.createRoot(rootElement);
   root.render(
     <React.StrictMode>
       <App />
     </React.StrictMode>
   );
   ```

   src/tsx/app.tsx
   ```tsx
   import React from 'react';

   const App: React.FC = () => {
     return (
       <div>
         <section>
           <h1 className="text-3xl font-bold underline text-center">Test</h1>
         </section>
         <section className="grid gap-4 p-4">
           <div className="grid grid-cols-3 items-center gap-4 font-bold text-lg">
             <h2 className="text-center">Size</h2>
             <h2 className="text-center">Primary</h2>
             <h2 className="text-center">Secondary</h2>
           </div>
           <div className="grid grid-cols-3 items-center gap-4 text-center">
             <h2 className="text-lg font-semibold">Large</h2>
             <button className="bg-blue-500 text-white py-2 rounded w-40">Button</button>
             <button className="bg-white text-black border border-black py-2 rounded w-40">Button</button>
           </div>
           <div className="grid grid-cols-3 items-center gap-4 text-center">
             <h2 className="text-lg font-semibold">Medium</h2>
             <button className="bg-blue-500 text-white py-2 rounded w-32">Button</button>
             <button className="bg-white text-black border border-black py-2 rounded w-32">Button</button>
           </div>
           <div className="grid grid-cols-3 items-center gap-4 text-center">
             <h2 className="text-lg font-semibold">Small</h2>
             <button className="bg-blue-500 text-white py-2 rounded w-24">Button</button>
             <button className="bg-white text-black border border-black py-2 rounded w-24">Button</button>
           </div>
         </section>
       </div>
     );
   };

   export default App;
   ```
   <br>

5. **input.cssの作成**
   src/css/input.css
   ```css
   @tailwind base;
   @tailwind components;
   @tailwind utilities;
   ```
   <br>

6. **tailwind.config.jsの修正**
   contentを以下の様に修正します。
   ```js
   module.exports = {
     content: ['./src/**/*.{html,tsx}'],
     // …以下省略
   };
   ```
   <br>

7. **output.cssの修正**
   input.cssから最終的なoutput.cssを生成します。
   ```bash
   npx tailwindcss -i ./src/css/input.css -o ./src/css/output.css
   ```
   <br>

8. **package.jsonにstartスクリプトを追加**
   package.jsonにViteを使った開発サーバーを起動するstartスクリプトを追加します。
   ```json
   {
     "name": "tailwind-variants",
     "version": "1.0.0",
     "scripts": {
       "start": "vite"
     },
     "dependencies": {
     }
   }
   ```
   <br>

   これでプロジェクト構成は以下になっています。
   ```
   project_root/
   ├── node_modules/
   ├── src/
   │   ├── css/
   │   │   ├── input.css
   │   │   └── output.css
   │   └── tsx/
   │       ├── app.tsx
   │       └── index.tsx
   ├── package.json
   ├── tailwind.config.js
   └── tsconfig.json
   ```

   プロジェクトのルートディレクトリで以下のコマンドを実行します。

   ```bash
   npm start
   …
   Local:   http://localhost:5173/
   ```

   該当のURLをブラウザで開き、「はじめに」の画像にあるボタン群が表示されれば完了です。


## Tailwind Variantsの導入
1. **Tailwind Variantsのインストール**

   ```bash
   npm install tailwind-variants
   ```
   <br>

2. **Reactファイルの作成**
   「[事前準備](#事前準備)」で用意した`src/tsx/app.tsx`を以下の様に修正します。
   ```tsx
   import React from 'react';
   import { tv } from 'tailwind-variants';

   const button = tv({
     base: 'py-2 rounded text-center',
     variants: {
       color: {
         primary: 'bg-blue-500 text-white',
         secondary: 'bg-white text-black border border-black',
       },
       size: {
         sm: 'w-24',
         md: 'w-32',
         lg: 'w-40',
       },
     },
     defaultVariants: {
       color: 'primary',
       size: 'md',
     },
   });

   const App: React.FC = () => {
     return (
       <div>
         <section>
           <h1 className="text-3xl font-bold underline text-center">app_after.tsx</h1>
         </section>
         <section className="grid gap-4 p-4">
           <div className="grid grid-cols-3 items-center gap-4 font-bold text-lg">
             <h2 className="text-center">Size</h2>
             <h2 className="text-center">Primary</h2>
             <h2 className="text-center">Secondary</h2>
           </div>
           <div className="grid grid-cols-3 items-center gap-4 text-center">
             <h2 className="text-lg font-semibold">Large</h2>
             <button className={button({ color: 'primary', size: 'lg' })}>Button</button>
             <button className={button({ color: 'secondary', size: 'lg' })}>Button</button>
           </div>
           <div className="grid grid-cols-3 items-center gap-4 text-center">
             <h2 className="text-lg font-semibold">Medium</h2>
             <button className={button({ color: 'primary', size: 'md' })}>Button</button>
             <button className={button({ color: 'secondary', size: 'md' })}>Button</button>
           </div>
           <div className="grid grid-cols-3 items-center gap-4 text-center">
             <h2 className="text-lg font-semibold">Small</h2>
             <button className={button({ color: 'primary', size: 'sm' })}>Button</button>
             <button className={button({ color: 'secondary', size: 'sm' })}>Button</button>
           </div>
         </section>
       </div>
     );
   };

   export default App;
   ```
#### `tv()`関数

`tv()`は、Tailwind Variantsが提供するクラス名生成関数で、動的にクラスを生成するために使用されます。引数には以下の要素を設定します。

1. **`base`**  
   これは、すべてのボタンに共通して適用される基本のスタイルを定義します。上記の例では、`'py-2 rounded text-center'`が基本スタイルとして設定されています。これにより、すべてのボタンに対して、一定のパディング、角丸、およびテキストの中央揃えが適用されます。

2. **`variants`**  
   ここでは、動的に変更できるクラスのバリエーションを定義します。上記の例では、`color`と`size`の2つのバリエーションが設定されています。
   - `color`: `primary`と`secondary`の2つのオプションがあり、それぞれ異なる背景色や文字色が設定されています。
   - `size`: `sm`, `md`, `lg`の3つのオプションがあり、それぞれ異なるボタンの幅が設定されています。

3. **`defaultVariants`**  
   これは、`variants`で定義したバリエーションのデフォルト値を設定します。上記の例では、`color`が`primary`、`size`が`md`に設定されており、`tv()`を呼び出す際に引数を省略すると、これらのデフォルト設定が適用されます。

#### 使用方法

`tv()`関数を使って、ボタンに動的にスタイルを適用するには、以下のようにクラスを呼び出します。

```tsx
<button className={button({ color: 'primary', size: 'lg' })}>Button</button>
```
<br>

修正後の状態で再度ブラウザで表示すると、修正前と同じようにボタンが表示されます。

## 所感
Tailwind Variantsの導入についてまとめてみました。
Tailwind Variantsを使用することで動的なスタイリングを簡潔に管理でき、スタイルの管理が非常に効率的になっているように感じました。
今回紹介した機能はあくまで機能の一部なので、興味がある方は是非触ってみて下さい。

## 参考資料
https://www.tailwind-variants.org/