2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Tailwind CSSを使いこなすための小技集 ベスト3

Last updated at Posted at 2024-05-09

はじめに

React,Vueなどモダンな開発言語のサポート役として、もはや必須と言っても過言ではないのがTailwind CSS。ただ、通常のCSSやSCSSからTailwindを使いはじめた頃って、なんか違和感があって使いづらさを感じると思います。自分もそうでした。しかし、使い慣れてくるともう素のCSSには戻れない、というくらい圧倒的な柔軟性とスタイリングのしやすさ、そして開発効率の良さを感じてきます。

今回はそのTailwind CSSの能力を爆上げする小技集を、実用例と一緒に紹介していきたいと思います。

その1 動的にスタイリング 難易度 ☆

こちらはよく使用されるので初心者の方でも見たことある人は多いのではないでしょうか?
三項演算子を利用して条件によって当てたいスタイルを変更する使い方となります。

import React, { useState } from 'react';

function App() {
  const [isActive, setIsActive] = useState(false);

  return (
    <div className="flex items-center justify-center h-screen">
      <button
        className={`py-2 px-4 rounded ${isActive ? 'bg-blue-500 text-white' : 'bg-gray-300 text-black'}`}
        onClick={() => setIsActive(!isActive)}
      >
        Toggle Style
      </button>
    </div>
  );
}

export default App;

 

その2 clsxとtwMergeを使って動的にスタイリング 難易度 ☆☆

clsxは可変長引数として受け取った文字列を結合してクラス名を作成します。
別の言い方をすると条件付きでスタイルを変更するためのユーティリティです。

その1の三項演算子を使用した場合との違いは以下の通りです。

主な違い

可読性と管理性:

テンプレートリテラル
三項演算子を使用する場合のようなシンプルな条件付きロジックは簡単ですが、長くなったり複雑になると読みづらくなります。

clsxとtwMerge
複雑な条件付きロジックや多くのクラス名を扱う場合に可読性と管理性が向上します。

手順

難しく聞こえますがめちゃくちゃ簡単で、大きく分けて3ステップで使用可能です🚀
では、そっこうで解説してきます。

①インストール

npm install --save clsx

②インポート

lib/utils.ts

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

これはもう書き方みたいな感じなのであまり細かいことは気にしなくて、ひとまず書いちゃいましょう。

解説

twMergeはそのまんまですがTailwind CSSのクラスをマージして重複や矛盾するクラスを整理するために使用されます。

例えば同じクラスが複数回指定されている場合、それを一つにまとめてくれたり、同じプロパティに対して異なる値が指定されている場合、最後に指定されたクラスを優先します。

注意点

cnはclsxとtwMergeを組み合わさったもので、条件付きクラス適用に加えて、クラスの重複や競合を解決してくれますが、clsx単体で使用する場合では条件付きクラス適用とクラス名の結合を行うだけで、クラスの重複や競合は解決されません(使用例2のパターン)のでご注意ください。

③使いたい場所で使用してみよう

次から使用例をパターン別にみていきましょう

使用例1 シンプルなパターン

import { cn } from "@/lib/utils";

 <div className={cn('lg:h-[140px] h-[100px] border-t-2', // 共通のスタイル
        status === 'correct' && 'border-transparent bg-green-100',
        status === 'wrong' && 'border-transparent bg-rose-100',
    )}>
 </div>

上記では共通のスタイルがlg:h-[140px] h-[100px] border-t-2の部分で、それにプラスしてcorrectの場合はborder-transparent bg-green-100が、wrongの場合はborder-transparent bg-rose-100が動的に当たるようになっています。

使用例2 ボタンのスタイル

条件によりボタンのスタイルを変更する

import clsx from 'clsx';

function Button({ isActive, isDisabled }) {
  const buttonClass = clsx(
    'px-4 py-2 rounded',  // 基本クラス
    {
      'bg-blue-500 text-white': isActive,     // アクティブな時のクラス
      'bg-gray-500 text-gray-200': isDisabled, // 無効な時のクラス
      'bg-green-500 text-white': !isActive && !isDisabled // デフォルトクラス
    }
  );

  return (
    <button className={buttonClass} disabled={isDisabled}>
      Button
    </button>
  );
}

使用例3 ナビゲーションリンク

現在のページに応じてスタイルを変更するナビゲーションリンクの例

import clsx from 'clsx';
import { useLocation } from 'react-router-dom';

function NavLink({ to, children }) {
  const location = useLocation();
  const isActive = location.pathname === to;

  const linkClass = clsx(
    'text-gray-700 px-3 py-2 rounded-md text-sm font-medium',  // 基本クラス
    {
      'bg-gray-900 text-white': isActive,  // アクティブな時のクラス
      'hover:bg-gray-700 hover:text-white': !isActive, // ホバー時のクラス
    }
  );

  return (
    <a href={to} className={linkClass}>
      {children}
    </a>
  );
}

使用例4 ダークモードの切り替え

import clsx from 'clsx';

function Card({ isDarkMode }) {
  const cardClass = clsx(
    'p-4 rounded-lg shadow-md',  // 基本クラス
    {
      'bg-white text-black': !isDarkMode,  // ライトモードのクラス
      'bg-gray-800 text-white': isDarkMode // ダークモードのクラス
    }
  );

  return (
    <div className={cardClass}>
      <h2 className="text-xl font-bold">Card Title</h2>
      <p className="mt-2">Card content goes here...</p>
    </div>
  );
}

その3 useState, useEffectを使用して無敵レスポンシブ 難易度 ☆☆☆

こちらは説明するよりコードでまずはご確認ください。
※実際に使ったコードを使用してますので、関係ない部分はスルーして大丈夫です。

import { useEffect, useState } from "react";

interface statisticsCard {
    text: string;
    numbers: number;
    icon: any;
}

const Statistics = () => {
    const staticticsCrd: statisticsCard[] = [
        {text: '全てのタスク', numbers: 15, icon: faDiagramProject},
        {text: '完了済みのタスク', numbers: 30, icon: faListCheck},
        {text: 'カテゴリー', numbers: 3, icon: faLayerGroup},
    ];

    const { isDark, setIsDark } = useGlobalContextProvider();
    const [currentWidth, setCurrentWidth] = useState<number>(window.innerWidth);

    useEffect(() => {
        function handleResize(){
            setCurrentWidth(window.innerWidth);
        }

        window.addEventListener('resize', handleResize);
        return () => {
            window.addEventListener('resize', handleResize);
        }
    }, [currentWidth]);

  return (
    <div className={`${isDark ? "bg-blackColorDark" : "bg-white"} m-5 p-8 rounded-lg flex gap-4`}>
      {staticticsCrd.map((singleCard, cardIndex) => (
        <div key={cardIndex}>
            <Card singleCard={singleCard} currentWidth={currentWidth}/>
        </div>
      ))}
    </div>
  );
}

export default Statistics

const Card = ({singleCard, currentWidth}: {singleCard: statisticsCard, currentWidth:number}) =>  {
    const {text, numbers, icon} = singleCard;
    return (
        <div className={`${currentWidth < 1318 ? "gap-6" : "gap-11"}
            px-4 p-3 rounded-md text-white bg-mainColor flex items-center gap-12`}>
            <div className={`${currentWidth < 750 ? "items-center" : ""} flex flex-col`}>
                <span className="font-bold text-3xl">{numbers}</span>
                <span className={`${currentWidth < 750 ? "text-center" : ""}
                    font-light text-[12px]`}>{text}</span>
            </div>
            <div className={`${currentWidth < 750 ? "hidden" : ""}
                    h-12 w-12 rounded-full bg-white flex items-center justify-center`}>
                <FontAwesomeIcon
                className="p-7 text-mainColor"
                icon={icon}
                />
            </div>
        </div>
    )
}

解説

ステップ1 useState及びuseEffectで設定準備

これはこの書き方で統一できますので覚えちゃいましょう👍
上記コードで言う以下の部分にあたります。

簡単に説明するとwindow幅を監視してリサイズ時に幅を更新するための基本的なReactフックを実装してます。これによりレスポンシブなデザインを動的に管理することができます。

    const [currentWidth, setCurrentWidth] = useState<number>(window.innerWidth);
    
    useEffect(() => {
            function handleResize(){
                setCurrentWidth(window.innerWidth);
            }
    
            window.addEventListener('resize', handleResize);
            return () => {
                window.addEventListener('resize', handleResize);
            }
        }, [currentWidth]);

ステップ2 propsとして使用するコンポーネントに渡す

以下の部分です。
見たまんまですがcurrentWidthを渡しています。

    const Card = ({singleCard, currentWidth}:
        {singleCard: statisticsCard, currentWidth:number}) =>  {
        return (
            // 省略
        )

ステップ3 使用したいタグ内で自由に数値を設定してスタイリングする

これが以下の部分です。

    <div className={`${currentWidth < 1318 ? "gap-6" : "gap-11"}
        px-4 p-3 rounded-md text-white bg-mainColor flex items-center`}>
        <div className={`${currentWidth < 750 ? "items-center" : ""} flex flex-col`}>
            <span className="font-bold text-3xl">{numbers}</span>
            <span className={`${currentWidth < 750 ? "text-center" : ""}
                font-light text-[12px]`}>{text}</span>
        </div>
        <div className={`${currentWidth < 750 ? "hidden" : ""}
                h-12 w-12 rounded-full bg-white flex items-center justify-center`}>
            <FontAwesomeIcon
            className="p-7 text-mainColor"
            icon={icon}
            />
        </div>
    </div>

例えばこの部分ではwindow幅が1318px未満の場合はgap-6クラスを、そうでない場合はgap-11クラスを適用しています。それ以外の部分は共通のスタイルになります。

    className={`${currentWidth < 1318 ? "gap-6" : "gap-11"}
    px-4 p-3 rounded-md text-white bg-mainColor flex items-center`}

これが使いこなせるようになると、幅を自由に好きな数値で指定できるのでレスポンシブが柔軟に対応できるようになります。

まとめ

という感じで大きく分けて3パターンのTailwindうを使用した動的なスタイリング方法でした。

propsを渡しまくったり、ユーザーのstatusやコンポーネントの状態がどんどん変わっていくような、複雑なアプリケーション開発ではめちゃくちゃ重宝しますので、是非、使ってみてください。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?