[Hugo] テーマ切り替えボタン(ダークモードトグル)の修正・カスタム

はじめに

Hugo + PaperModでサイト制作を始めてから、約1か月程経過。

初期にHugo + PaperModでサイトを動かした際には動いてたはずのテーマ切り替えボタンが動かなくなってたので、今日はその修正と、デフォルトのだったアイコンを変更して見たいと思います。

ブログ記事として価値があるのは、テーマアイコンのカスタムになるかと思いますが、問題修正に関しても、構造を理解する必要があるので、Hugoの内部構造理解を深める内容になるかもしれません。

いつも備忘録メモとして記事を書いていますが、情報が誰かのお役に立てたら幸いです。

月アイコンの正式名称・呼び方について


記事を書きはじめて最初に躓いたのはhugoで右上に表示されている、月のアイコンの名称。
正式名称を検索するもそれらしい情報が見つからなかったのでAIに聞いてみました。


ai icon
🌓 月アイコンの正式名称・呼び方について
一般的に以下のような名称・キーワードで呼ばれています

呼び方 解説 用途例
ダークモードトグル(Dark Mode Toggle) 最も一般的な呼称(「テーマ切り替えボタン」) 記事タイトル、解説文で使いやすい
テーマ切り替えボタン(Theme Switch / Theme Toggle) 明るい・暗いテーマの切り替えボタンという意味合い より初心者向けにも通じやすい
月アイコン(Moon Icon) デザイン上の見た目を直接表現(カジュアル寄り) UIの見た目の説明用などに使う
ライト/ダークモードスイッチ(Light/Dark Mode Switch) 機能名として使われることもある 英語での文脈に合う
モードトグルボタン(Mode Toggle Button) 技術ドキュメントなどで時折見かける 少し抽象的だが正確

1. 問題の概要

現在、ブログの右上にテーマ切り替えボタンが表示されていますが、ボタンをクリックしても動作していないようです。
何時からそうだったか忘れましたが、問題発生後、結構時間がたっていて、他の事に夢中で修正を後回しにしてました。

反応しない所を見ると、JavaScriptがらみの問題な気がします。

[Hugo] テーマ切り替えボタン(ダークモードトグル)の「修正」と「カスタム」:問題の概要

2. 原因調査


ai icon

🔧 ここで調べていただけるとスムーズです:

  • config.yml に ShowThemeToggle: true があるか?
  • 右上の月アイコン部分のHTMLタグ(id=“theme-toggle” がある?)
  • main.js(または該当のJS)にテーマ切り替え処理が含まれているか?

テーマ切り替えボタンのコード:
  • layouts\partials\header.html
<!-- テーマ切り替えボタン -->
<div class="logo-switches">
  {{ if not site.Params.disableThemeToggle }}
  <button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
    <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
      fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
      stroke-linejoin="round">
      <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
    </svg>
    <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
      fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
      stroke-linejoin="round">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
    </svg>
  </button>
  {{ end }}
</div>

設定ファイルの該当項目

  • config.toml
[params]
  author = "lain"
  defaultTheme = "auto"
  hideFooter = false
  ShowReadingTime = true
  ShowCodeCopyButtons = true
  ShowShareButtons = true    # <- 該当項目
  disableThemeToggle = false  # <- 該当項目

main.js や theme-toggle.js については、ブログ全体のフォルダ内を検索しましたが該当ファイルは見つからない。

  • DeveloperToolでエラーはあるか?

user icon

テーマボタンをクリックして、エラーがでてるか確認しましたが、なにもないようです。 giscus.appがらみのエラーは、多分、GithHUBのリポジトリを非公開にしたことで、出てる物だと思いますので、今回の件とは別ですね。

DeveloperToolでエラー確認

ai icon

✅ 現時点の重要ポイント

・ giscus.app の404エラーは GitHub Discussionsが非公開化された影響なので、今回のダークモード問題とは無関係です。
・ ダークモードボタンをクリックしても、JavaScriptのエラーは出ていない
→ つまり 「そもそも何も起きてない(イベント未登録)」可能性が高いです。


ai icon
現状のまとめ
✅ HTML id=“theme-toggle” のボタンあり(OK)
✅ config.toml disableThemeToggle = false、defaultTheme = “auto”(OK)
❌ JavaScript main.js や theme-toggle.js が存在しない(NG)

結論:
ボタンは表示されてるけど、押してもJavaScriptが反応しない → スクリプトが読み込まれていないため。

🛠 解決策:自作でトグルスクリプトを追加する(おすすめ)
PaperModの切替処理は非常にシンプルです。以下のようなJSを static/js/theme-toggle.js として作成し、baseof.html か footer.html などで読み込ませるだけでOKです。

補足: 何故動かなくなったか?

なぜ初期の頃、動いていた物が動かなくなったかについてですが、
当初、PaperModテーマを導入しボタンの動作を確認しましたが、サイトをカスタムしていく中で、テーマファイルを多数使わなくなり、 独自に

  • layouts/_default/(original file)
  • layouts/partials/(original file)
  • layouts/shortcodes/(original file)

などを作成したことで、テーマで機能していたJavaScriptを使わなくなってしまったせいだと思われます。

3. 解決策


ai icon
🛠 解決策:自作でトグルスクリプトを追加する

PaperModの切替処理は非常にシンプルです。
以下のようなJSを static/js/theme-toggle.js として作成し、baseof.html か footer.html などで読み込ませるだけでOKです。

1.static/js/theme-toggle.js を作成

 static/js/theme-toggle.js

document.addEventListener("DOMContentLoaded", function () {
  const themeToggle = document.getElementById("theme-toggle");
  if (!themeToggle) return;

  const currentTheme = localStorage.getItem("pref-theme");
  if (currentTheme === "dark") {
    document.body.classList.add("dark");
  } else if (currentTheme === "light") {
    document.body.classList.remove("dark");
  }

  themeToggle.addEventListener("click", function () {
    const isDark = document.body.classList.toggle("dark");
    localStorage.setItem("pref-theme", isDark ? "dark" : "light");
  });
});

footer.html の末尾に読み込みを追加

 layouts/partials/footer.html

<script src="/js/theme-toggle.js"></script>

再ビルドして確認

右上の月ボタンを押して body に .dark クラスが付けば成功です。
(DevTools → Elements → にクラスが追加されるのを確認)

4. 解決と課題

user icon
ありがとうございます、動くようになりました。 ただ、独自カスタムを多数したことで、ダークモード用にCSSを独自に多数書き換える必要がありそうで、大仕事ですね…。
解決と課題 解決と課題

ai icon

おおー! スクショ確認しました。
ダークモード切り替えボタンがしっかり機能しているのがわかります!
🎉 まずは動作修正、完全に成功です。おめでとうございます!

🌓 次の課題:「独自カスタムとCSSの整合性調整」
確かに、現在のサイトは明るい背景を前提とした独自スタイルが多く含まれているため、ダークモードにすると以下のようなギャップが出やすいです:

🧩 ありがちなダークモードの問題点

問題 内容 対処法の方向性
背景が黒でもテキストやボタンがそのまま(薄すぎ・濃すぎ) .card.section の背景色が #fff に固定されている .dark .card { background-color: #1e1e1e; } など追加
カルーセルの矢印などが浮いて見える アイコンやSVGに fill="#000" が指定されている fill: currentColor; に変更し、文字色に準拠させる
青などの強いカラーが背景と喧嘩する .btn-blue.badge の色が視認性低下 .dark .btn-blue { background-color: desaturate(...) } などで調整



user icon
ありがとうございます。
CSSのカスタムは今後少しずつ対応していくとして、次の課題であるテーマアイコンのカスタムについて進めていきたいと思います。

ボタンの状態(太陽/月)を切り替え

「月🌙」「太陽☀️」 のアイコンを、現在のテーマに応じて切り替える機能を追加して、より自然で直感的なUIにしていきます。

  • ダークモード → **太陽アイコン(☀️)**を表示
  • ライトモード → **月アイコン(🌙)**を表示
  • 状態はテーマの切り替え時に連動

既存のHTML構造を活かす形で対応

すでに id=“moon” と id=“sun” の <svg> があるので、CSSとJSの両面で切り替え処理を加えます。

1. CSS:非表示にするスタイルを追加

まずは custom.css にこのようなスタイルを追加します:

#sun,
#moon {
  display: none;
}

body.dark #sun {
  display: inline;
}

body:not(.dark) #moon {
  display: inline;
}

ポイント:

  • デフォルトで両方非表示にし、
  • 現在の body クラス(dark の有無)で表示を切り替える

2. JavaScript:初期状態に応じてクラスを反映

theme-toggle.js の先ほどのコードに、以下の部分を加えてください:

document.addEventListener("DOMContentLoaded", function () {
  const themeToggle = document.getElementById("theme-toggle");
  if (!themeToggle) return;

  // 初期テーマ状態に合わせてクラスを適用
  const currentTheme = localStorage.getItem("pref-theme");

  if (currentTheme === "dark") {
    document.body.classList.add("dark");
  } else if (currentTheme === "light") {
    document.body.classList.remove("dark");
  } else {
    // OS設定に従う場合
    if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
      document.body.classList.add("dark");
    }
  }

  themeToggle.addEventListener("click", function () {
    const isDark = document.body.classList.toggle("dark");
    localStorage.setItem("pref-theme", isDark ? "dark" : "light");

    // クラスの切り替えによってアイコン表示もCSSで変わる
  });
});

動作確認のポイント


 1.ライトモードでは🌙が表示されているか?
 2.クリックすると☀️に切り替わるか?
 3.リロードしても、前回の状態が保持されるか?


カスタムアイコン化

目的

・<button> を <a> や <span> などに変更して見た目を統一
・アイコンは独自の SVG または画像に差し替え
・クリックでダークモード切替は維持

SVGとは

AIによる解説:

ai2 icon

Scalable Vector Graphics(スケーラブル・ベクター・グラフィックス)の略で、Web上で拡大・縮小しても画質が劣化しないベクター形式の画像ファイルのことです.
XMLベースで記述され、テキストエディタでも編集可能です.

ベクター形式:
SVGは、点を結ぶ線や曲線の方程式で画像を表現するため、拡大・縮小しても画質が劣化しません.

XMLベース:
SVGファイルは、XMLというマークアップ言語で記述されています。そのため、テキストエディタで直接編集したり、プログラムで生成したりすることが可能です.

Webでの利用:
SVGは、Webサイトでロゴやアイコン、イラスト、グラフなどを表示する際に広く利用されています.

HTML & CSS

変更前:

<button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
  <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
    fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
    stroke-linejoin="round">
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
  </svg>
  <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
    fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
    stroke-linejoin="round">
    <circle cx="12" cy="12" r="5"></circle>
    <line x1="12" y1="1" x2="12" y2="3"></line>
    <line x1="12" y1="21" x2="12" y2="23"></line>
    <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
    <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
    <line x1="1" y1="12" x2="3" y2="12"></line>
    <line x1="21" y1="12" x2="23" y2="12"></line>
    <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
    <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
  </svg>
</button>

変更後:

<a href="javascript:void(0);" id="theme-toggle" class="theme-icon" title="テーマ切替">
  <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
    <!-- カスタムの月アイコンパス -->
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
  </svg>
  <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
    <!-- カスタムの太陽アイコン -->
    <circle cx="12" cy="12" r="5"></circle>
    <line x1="12" y1="1" x2="12" y2="3"></line>
    <line x1="12" y1="21" x2="12" y2="23"></line>
    <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
    <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
    <line x1="1" y1="12" x2="3" y2="12"></line>
    <line x1="21" y1="12" x2="23" y2="12"></line>
    <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
    <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
  </svg>
</a>
.theme-icon {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: none;
  box-shadow: none;
  text-decoration: none;
}

.theme-icon svg {
  width: 24px;
  height: 24px;
  transition: opacity 0.3s ease;
}

#sun,
#moon {
  display: none;
}

body.dark #sun {
  display: inline;
}

body:not(.dark) #moon {
  display: inline;
}

※ display: inline-flex を使えば、インライン扱いで中央揃えもしやすくなります

カスタムSVGに差し替えるには?

好きなSVGを https://iconoir.com/https://phosphoricons.com/ で探して

<svg>…</svg> をコピーして差し替えるだけでOKです。

たとえば:

  • 🌓 moon-stars-fill や weather-night
  • ☀️ sun-fill, sun-beam, light-mode

絵文字

  1. HTMLの構造変更(SVGを除去)
<a href="javascript:void(0);" id="theme-toggle" class="theme-icon" title="テーマ切替">
  <span id="moon">🌙</span>
  <span id="sun">☀️</span>
</a>
  1. CSSで切り替えスタイルを制御
#sun,
#moon {
  display: none;
}

body.dark #sun {
  display: inline;
}

body:not(.dark) #moon {
  display: inline;
}

.theme-icon {
  font-size: 20px;
  cursor: pointer;
  text-decoration: none;
}

💡 メリット

項目 説明
✅ シンプル CSSやSVGの細かい調整不要
✅ 見やすい 各プラットフォームで明確な表示が保証される
✅ 軽量 アイコンファイル不要、HTMLに直接埋め込める
✅ モバイルでも自然 ユニバーサルデザインに近い扱いができる

トグルのアニメーション

user icon
提案のあった、トグルのアニメーションについてですが、現状の実装だとどのような実装例がありますでしょうか?
テーマアイコンをクリックすると、現状では一瞬で色が切り替わりますが、ゆっくりと切り替わるような実装可能でしょうか?

ai icon

すごく良い着眼点です! UIの「完成度を上げる最後のひと押し」が、まさにトグルのアニメーションとテーマ切り替えのスムーズ化です✨

🎯 目的の再整理

  1. テーマ切り替え時(ライト⇔ダーク)に色がふわっと切り替わる
  2. ☀️/🌙 アイコン自体の表示も、フェードやスライドなどでアニメーション

✅ 実装例①:背景色・文字色の切り替えをフェードでスムーズに これはCSSトランジションで実現できます。

🔧 スタイル追加(custom.css に)

body {
  transition: background-color 0.5s ease, color 0.5s ease;
}

.card, .navbar, .section, .post-content {
  transition: background-color 0.5s ease, color 0.5s ease;
}

✅ 実装例②:🌙/☀️ アイコン自体にフェード・スライドアニメーション SVGまたは絵文字を で2つ重ねて、切り替える方式なら以下でOK:

🔧 CSSを追加

#sun,
#moon {
  opacity: 0;
  transition: opacity 0.3s ease, transform 0.3s ease;
  transform: scale(0.95);
  display: inline-block;
  position: absolute;
}

body.dark #sun {
  opacity: 1;
  transform: scale(1);
  position: static;
}

body:not(.dark) #moon {
  opacity: 1;
  transform: scale(1);
  position: static;
}

.theme-icon {
  position: relative;
  display: inline-block;
  width: 24px;
  height: 24px;
}

✅ HTML構造も対応済みのままでOK:

<a href="javascript:void(0);" id="theme-toggle" class="theme-icon">
  <span id="moon">🌙</span>
  <span id="sun"></span>
</a>

💡 オプション:アイコンの「回転」や「スライド」も可能! たとえば:

#sun {
  transform: rotate(-90deg);
}
body.dark #sun {
  transform: rotate(0deg);
}
#moon {
  transform: translateY(-10px);
}
body:not(.dark) #moon {
  transform: translateY(0);
}

✅ 補足:ページ全体に適用するなら html or body にアニメ付きクラスをつけて制御しても良い

body.theme-transition {
  transition: all 0.3s ease;
}
// JavaScript内で切替時に一時的に class を付ける
document.body.classList.add("theme-transition");
setTimeout(() => {
  document.body.classList.remove("theme-transition");
}, 300);

🎉 まとめ:おすすめ実装

要素 実装内容
色の切り替え transition: background-color, colorbody と各セクションに
アイコン切り替え opacitytransform#sun / #moon に適用
ページ全体を柔らかく切り替えたい場合 .theme-transition クラスを JS から一時的に付与

ai icon

このようにアニメーションを加えることで、視覚的な洗練度が一段アップします。 ご希望あれば「回転・スライド」「クロスフェード」「タイミング制御」などさらに細かい調整も可能ですよ!

どんな表現が一番サイトに合いそうでしょうか?(例:優しいフェード、元気なスライドなど)

サイトカラーに合わせたスタイル調整

CSSの修正項目が多いので、今後少しずつ対応する予定です。
やや大仕事。

localStorageでテーマ設定を保持


user icon
ありがとうございます。
最後に、あなたから提案のあった
『localStorageを使った記憶機能の補足』
についてお伺いしたいです。

ai icon
最後に「localStorage を使ったテーマ記憶機能」について、しっかり補足しますね。



🧠 localStorageによるテーマ記憶機能とは?

目的:
ブラウザを再読み込みした後も、前回選んだライト/ダークテーマを保持しておく

OSのダークモード設定ではなく、ユーザーの選択を優先する

どういう仕組み?

ユーザーが☀️/🌙をクリックしたら、localStorage.setItem(“pref-theme”, “dark”) などで記憶

ページ読み込み時に、localStorage.getItem(“pref-theme”) を読み出してテーマを自動で反映

すでに使っている基本コード(確認)

あなたが使っていた theme-toggle.js に、このような処理が入っていればOKです:

// ページ読み込み時に適用
const currentTheme = localStorage.getItem("pref-theme");

if (currentTheme === "dark") {
  document.body.classList.add("dark");
} else if (currentTheme === "light") {
  document.body.classList.remove("dark");
} else {
  // 初回訪問時:OSの設定に従う
  if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
    document.body.classList.add("dark");
  }
}

// ボタンで切り替えたときに保存
themeToggle.addEventListener("click", function () {
  const isDark = document.body.classList.toggle("dark");
  localStorage.setItem("pref-theme", isDark ? "dark" : "light");
});

よくある誤解

誤解 説明
「自動でダークになる」=OSの設定だけが動いてる? localStorage が優先されるので、自分で切り替えたらその設定が保持される
Cookieが関係してる? → いいえ、localStorageはブラウザの内部に保存され、ページをまたいでも保持されます(ただし端末ごと)

確認方法(Chromeなど)

  1. DevToolsを開く(F12)
  2. [Application] タブ → 左側で「Local Storage」→ http://localhost:1313 を選択
  3. pref-theme というキーがあり、値が “dark” または “light” になっていればOK!

ai icon
もし、将来的に「OS優先か、記憶優先かを選ばせたい」などの設定UIを追加するなら、 pref-theme = “auto” を新たに設けて分岐させる方法もあります。