Nuxt 3 でのダークモード対応

このサイトをダークモード切り替えに対応した。前回の記事で「Nuxt 3の話をどんどん発信してくれ」的なことを言ったので、言い出しっぺの自分がまずは書いてみる。

環境

  • nuxt 3.8.0
  • @nuxtjs/tailwindcss 6.8.1
    • tailwindcss 3.3.5
  • @nuxtjs/color-mode 3.4.4

仕様

@nuxtjs/color-modeはシステム設定に応じてテーマを出しわける機能や、light/dark以外の値も設定できる機能はあるがこのサイトは以下の仕様とした。

  • 初回訪問時はシステム設定に関わらずダークモードをオフとし、ダークモードにしたい人が能動的にオンに変更できる。
  • 切り替え状態は保持されて、次回以降の訪問では設定したカラーテーマで表示される。
  • 扱うテーマはダークモードがオフの場合とオンの場合の二種類のみ。

手順

  1. @nuxtjs/color-modeの導入
  2. スタイルの調整
  3. 切り替えボタンの設置

1. @nuxtjs/color-modeの導入

GitHub - nuxt-modules/color-mode: Dark and Light mode with auto detection made easy with Nuxt 🌗

Dark and Light mode with auto detection made easy with Nuxt 🌗 - nuxt-modules/color-mode

github.com favicongithub.com
GitHub - nuxt-modules/color-mode: Dark and Light mode with auto detection made easy with Nuxt 🌗
pnpm i --save-dev @nuxtjs/color-mode

nuxt.config.tsに以下を記述。

// nuxt.config.ts
export default defineNuxtConfig({
  ...,
  modules: [
    "@nuxtjs/color-mode",
  ],
  colorMode: {
    preference: "light",
    classSuffix: "",
  },
});

preferenceはデフォルトのsystemではなくlightを設定する。これによってシステム設定に関わらずダークモードオフになり仕様の1つ目を満たすことができる。

classSuffixについては、TailwindCSSが.darkというclassを要求するために空文字にする。 @nuxtjs/color-mode自体のデフォルトの設定は公式ドキュメントで確認できるが-modeというSuffixが付与されている(つまり.dark-modeというclassが付与される)。なのでTailwindCSS側に合わせて明示的に空文字を設定してあげる必要がある。

ここまでの設定で、@nuxtjs/color-modeによるダークモード切り替え時に、以下のようにhtml要素に.light/.dark classが付与されるようになる。

<!-- dark mode off -->
<html class="light">
  ...
</html>

<!-- dark mode on-->
<html class="dark">
  ...
</html>

tailwind.config.jsに以下の設定を記述することでTailwindCSS側ではdark:{class}で記述したクラスがダークモード時に反映されるようになる。

// tailwind.config.js
export default {
  ...,
  darkMode: "class",
};

2. スタイルの調整

このサイトはカラースキームとしてFlexokiを利用しているのだが、サイト内で定義されている通りの色をダークテーマの際にそのまま利用するとサイトの背景とコードブロックの背景・ボーダーとのコントラストが弱すぎたので、微調整したりしている。

例えばコードブロックは、ダークモードオフの場合は背景を#F2F0E5、ボーダーを#E6E4D9で定義している。これをダークモードオンにした場合は対応する色は背景が#1C1B1A、ボーダーが#282726となるのだが、このサイトではそれぞれ#343331と#575653にしている(色見本がなくて申し訳ない)。

この辺は定義よりも色々なデバイスを自分の目で見てしっくりくるものを採用したほうが良いと思っているので自分はそのようにした。

3. 切り替えボタンの設置

あとはヘッダに切り替えボタンの設置と、ボタン押下の際のロジックを実装すれば完了。NuxtUIの公式ドキュメントにあるダークモード切り替えの実装を流用させてもらった。

<script setup lang="ts">
const colorMode = useColorMode();
const isDark = computed({
  get() {
    return colorMode.value === "dark";
  },
  set() {
    colorMode.preference = colorMode.value === "dark" ? "light" : "dark";
  },
});

const toggleTheme = () => {
  isDark.value = !isDark.value;
};
</script>

<template>
  <header>
    <!-- 中略 -->
    <button>
      <img
        v-if="!isDark"
        src="~/assets/PhSunBold.svg"
        alt="dark mode off"
        width="24"
        height="24"
        @click="toggleTheme"
      />
      <img
        v-else
        src="~/assets/PhMoonBold.svg"
        alt="dark mode on"
        width="24"
        height="24"
        @click="toggleTheme"
      />
    </button>
  </header>
</template>

@nuxtjs/color-modeはデフォルトで設定の状態をLocalStrageに保存してくれているので、自前でその辺を実装する必要はなく単に切り替えだけで仕様の2つ目を満たすことができるのがありがたい。

最後に

@nuxtjs/color-modeの仕様やTailwindCSSでのダークモードの挙動などを調べながらの実装だったので実時間はそれなりにかかってしまったけど、終わってみれば結構シンプルに実装できた。

真っ当に読みやすいのはダークモードオフのほうだと思っているが、色合いとか雰囲気が好みなのはダークモードオンのほうなので自分は普段はオンにしている。皆さんはどっちが好きですか。

本筋ではないのでこの記事では割愛してしまったが、bodyAttrsでの文字色と背景色のクラスを指定してもコンポーネントに記述されてないのでPurgeされてしまうところで結構ハマったので別記事としてまとめる予定。