Blog

Blog

TailwindCSSでテーマ切り替えボタンを作る

公開日
2025-01-18

TailwindCSS+React+ReactIconsでテーマ切り替えボタンを作成してみました

はじめに

最初の記事です。
これから上がる全ての記事は勉強のアウトプットです。お見苦しい部分もあるかと思いますがお付き合い頂けると幸いです。

誤字とかは見逃してください。

作るもの

こんな感じのブラウザ上でテーマを切り替えられるボタンを作成します。
黄色の〇がずれてる?気のせいですよ。

使用技術とか

  • TypeScript
  • React
  • Tailwind CSS
  • React Icons
  • clsx

clsxは無くても大丈夫ですが、可読性爆上がりです。
行数書くとテンション上がるタイプの人間なので個人的に好きです。

コード全体

import { useState } from "react";
import { IoMoonOutline, IoSunnyOutline } from "react-icons/io5";
import clsx from "clsx";

const ThemeChangeBtn: React.FC = () => {
  const [isDark, setIsDark] = useState<boolean>(true); // テーマ判定ロジック

  const handleClick = () => {
    setIsDark((prev) => !prev);
    // テーマ切り替え処理
  };

  return (
    <div className={clsx(
      "p-px w-[71px] h-[34px] rounded-3xl relative",
      "border-2 border-spacing-1 border-amber-300 dark:border-amber-200",
    )}>
      <div
        onClick={handleClick}
        className={clsx(
          "cursor-pointer absolute top-[1px] left-[3px] w-full h-full z-10",
          "after:inline-block after:w-[28px] after:h-[28px] after:rounded-full",
          "after:bg-amber-300/30 dark:after:bg-amber-200/30",
          "after:transform after:transition after:duration-[400ms] after:ease-in-out",
          isDark ? "after:translate-x-[34px]" : "after:translate-x-0",
        )}
      />
      <IoSunnyOutline
        size={26}
        className={clsx(
          "absolute top-[2px] left-[3px]",
          "transform transition duration-200 ease-in-out",
          isDark ? "translate-x-17 opacity-0 scale-0" : "translate-x-0 opacity-100 scale-100",
        )}
      />
      <IoMoonOutline
        size={26}
        className={clsx(
          "absolute top-[2px] right-[3px]",
          "transform transition duration-200 ease-in-out",
          isDark ? "translate-x-0 opacity-100 scale-100" : "translate-x-17 opacity-0 scale-0"
        )}
      />
    </div>
  );
};

export default ThemeChangeBtn;

useStateの初期値にはテーマ判定のロジックを入れてください。
コード汚いとか、無駄じゃね?とかは受け付けていません。

細かく

外枠

    <div className={clsx(
      "p-px w-[71px] h-[34px] rounded-3xl relative",
      "border-2 border-spacing-1 border-amber-300 dark:border-amber-200",
    )}>
      // ...
    </div>

外枠を作成しています。
アイコンなどをabsoluteで配置するためにrelativeを指定しています。
border-spacing-1を指定するとおしゃれ感出ます。

ラベル

      <div
        onClick={handleClick}
        className={clsx(
          "cursor-pointer absolute top-[1px] left-[3px] w-full h-full z-10",
          "after:inline-block after:w-[28px] after:h-[28px] after:rounded-full",
          "after:bg-amber-300/30 dark:after:bg-amber-200/30",
          "after:transform after:transition after:duration-[400ms] after:ease-in-out",
          isDark ? "after:translate-x-[34px]" : "after:translate-x-0",
        )}
      />

こいつが一番頑張ってます。
外枠いっぱいに広げ、最前面に持ってきてクリック処理を受け付けます。cursor-pointerを指定するとイケメンですね。
疑似要素でわちゃわちゃしてる部分はちょっとずれてる黄色い〇の部分です。テーマ切り替え後に移動できるようにアニメーションを指定してあげます。

アイコン

      <IoSunnyOutline
        size={26}
        className={clsx(
          "absolute top-[2px] left-[3px]",
          "transform transition duration-200 ease-in-out",
          isDark ? "translate-x-17 opacity-0 scale-0" : "translate-x-0 opacity-100 scale-100",
        )}
      />
      // ほぼ同じなので略

アイコン部分です。位置の指定と、切り替え時のアニメーションを作成しています。
消える時に月に変わるように見せるために、移動しています。私には見えませんでした。

まとめ

いい感じのテーマ切り替えボタンを作りました。
アイコン部分の変わるように見えるアニメーションとか完全に自己満ですが楽しければOKです。

テーマ切り替えロジックはいいように実装してあげてください。
nextの場合は、参考になるかわかりませんがこのサイトのリポジトリからご覧下さい。

最終更新日
2025-01-26