ryuhei373373.3

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 の導入

https://github.com/nuxt-modules/color-mode

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 を設定する。これによってシステム設定に関わらずダークモードオフになり仕様の一つ目を満たすことができる。

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 に保存してくれているので、自前でその辺を実装する必要はなく単に切り替えだけで仕様の二つ目を満たすことができるのがありがたい。

最後に

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

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

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