2026年8月15日土曜日

ruby on railsで、テーマを切り替える例

<!-- テーマ切り替えボタン -->
<div class="theme-switcher" data-controller="theme">
  <button type="button"
          class="theme-btn"
          data-theme-value="normal"
          data-theme-target="button"
          data-action="click->theme#setNormal"
          title="ノーマルモード">
    <i class="fas fa-sun"></i>
  </button>

  <button type="button"
          class="theme-btn"
          data-theme-value="gray"
          data-theme-target="button"
          data-action="click->theme#setGray"
          title="グレーモード">
    <i class="fas fa-adjust"></i>
  </button>

  <button type="button"
          class="theme-btn"
          data-theme-value="dark"
          data-theme-target="button"
          data-action="click->theme#setDark"
          title="ダークモード">
    <i class="fas fa-moon"></i>
  </button>
</div>
ーーーーー     rails で、 これらの、ボタンを  使って、  Javascript  の、  トリガーを、    引くには、     どの   様な、     関連付けと、    スクリプト、   その他、   が、   必要に、   なりますか、

Rails(特に Hotwire + Stimulus)では、HTML側の

data-controller="theme"
data-action="click->theme#setDark"

が、JavaScript(Stimulus Controller)のメソッドを呼び出す仕組みになっています。

あなたのHTMLであれば、必要なものは以下の3つです。


① Stimulusを導入する

Rails 7なら通常は最初から入っています。

app/javascript/controllers/

というフォルダがあり、

application.js

import { Application } from "@hotwired/stimulus"
const application = Application.start()

となっていればOKです。

さらに

controllers/index.js

import { application } from "./application"
import ThemeController from "./theme_controller"
application.register("theme", ThemeController)

となっている必要があります。

※ Rails7のデフォルトでは autoload なので、自動登録されることもあります。


② theme_controller.js を作る

app/javascript/controllers/theme_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {

  static targets = ["button"]

  connect() {
    console.log("Theme controller connected")
  }

  setNormal() {
    console.log("Normal")
    document.body.className = "theme-normal"
  }

  setGray() {
    console.log("Gray")
    document.body.className = "theme-gray"
  }

  setDark() {
    console.log("Dark")
    document.body.className = "theme-dark"
  }

}

このようにすると、

クリック
setDark()
JavaScriptが実行される

という流れになります。


③ HTMLとの関連付け

質問に貼られているHTMLはそのままでOKです。

<div class="theme-switcher"
data-controller="theme">

これは

theme_controller.js

と結び付けられます。

そして

<button
data-action="click->theme#setDark">

theme_controller.js

setDark()

を呼び出します。

同様に

data-action="click->theme#setGray"

setGray()

になります。


④ ボタン(Target)も取得できる

HTMLでは

data-theme-target="button"

となっています。

Controllerでは

static targets = ["button"]

と書けば

this.buttonTargets

で全ボタンが取得できます。

例えば

setDark() {

  this.buttonTargets.forEach(button => {
    button.classList.remove("active")
  })

  event.currentTarget.classList.add("active")

}

のような処理もできます。


⑤ テーマを切り替える例

setDark() {
  document.documentElement.setAttribute("data-theme", "dark")
}

setGray() {
  document.documentElement.setAttribute("data-theme", "gray")
}

setNormal() {
  document.documentElement.setAttribute("data-theme", "normal")
}

CSSでは

:root[data-theme="dark"] {
  --bg-color: #222;
  --text-color: white;
}

:root[data-theme="gray"] {
  --bg-color: #ddd;
  --text-color: #333;
}

:root[data-theme="normal"] {
  --bg-color: white;
  --text-color: black;
}

body {
  background: var(--bg-color);
  color: var(--text-color);
}

とすれば、JavaScriptは「テーマ名を切り替えるだけ」で済み、CSS側で配色を管理できます。


全体の構成

app/
 └─ javascript/
     └─ controllers/
         ├─ application.js
         ├─ index.js
         └─ theme_controller.js   ← JavaScriptを書く

app/
 └─ views/
      ↓
      data-controller="theme"
      data-action="click->theme#setDark"

app/
 └─ assets/
     └─ stylesheets/
         theme.css

この構成にすると、

HTML(ボタン) → Stimulus Controller → JavaScript実行 → CSSテーマ切り替え

という流れで動作します。

はい、この theme.scss 自体は問題なく書けています

ただし、これだけでは動きません。 動かすためには、以下の項目がすべて揃っている必要があります。


theme_controller.js がある(✓)

これは既に作成済みですね。

app/javascript/controllers/theme_controller.js

② HTMLに data-controller が付いている(✓)

例えば

<div class="theme-switcher" data-controller="theme">

となっていればOKです。


③ ボタンに data-action が付いている(✓)

例えば

<button
class="theme-btn"
data-theme-value="dark"
data-theme-target="button"
data-action="click->theme#setDark">

これもOKです。


theme.scss が読み込まれている

ここが一番重要です。

Railsでは

app/assets/stylesheets/theme.scss

を置いただけでは、必ずしも読み込まれません。

application.scss の場合

もし

app/assets/stylesheets/application.scss

があるなら

@import "theme";

または(Dart Sassなら)

@use "theme";

が必要です。


application.css の場合

もし

/*
*= require_tree .
*= require_self
*/

なら

theme.scss

は読み込まれます。


⑤ CSS変数を実際に使う

例えば

body {
background: var(--body-bg);
color: var(--text-color);
}

あるいは

.main {
background: var(--main-bg);
}

のように、var(--...) を使っている必要があります。

もし

body {
background: white;
}

のように固定色を書いていると、

テーマを変えても色は変わりません。


⑥ ブラウザをリロードする

開発中は

Ctrl + Shift + R

などでキャッシュを無視してリロードすると、CSSやJavaScriptの変更が確実に反映されます。


⑦ Consoleで確認

ブラウザの開発者ツール(F12)の Console を開いて、

ボタンを押したときにエラーが出ていないか確認してください。

また、Elementsタブで <html> 要素を見て、

<html data-theme="dark">

<html data-theme="gray">

<html data-theme="normal">

のように data-theme の値がクリックに応じて変わるか を確認すると、JavaScriptが動いているかどうかがすぐに分かります。


確認したいファイル

もしまだ動かない場合は、次の3つを見せていただければ、原因をほぼ特定できます。

  1. app/assets/stylesheets/application.scss(または application.css
  2. テーマ切り替えボタンを置いているビュー(ERBファイル)
  3. ブラウザのConsoleに表示されるエラーメッセージ

この3つがあれば、「どこが原因で動かないのか」を具体的に確認できます。

vendor   が、   作られて、   いない場合、   どうすれば?、

vendor フォルダがない」という場合は、何を使って Rails を構築したかによって対応が変わります。

まず確認したいのですが、次のどちらでしょうか?

  • Rails 7 + Importmap を使っている
  • Rails 7/8 + esbuild・Vite・Webpack を使っている

もし Importmap を使っている場合

通常は

vendor/javascript

が作成され、stimulus などのライブラリが入ります。

もし存在しない場合は、ターミナルで

bin/rails importmap:install

を実行すると作成されます。


もし esbuild や Vite を使っている場合

この場合は vendor/javascript は不要です。

ライブラリは

node_modules/

から読み込まれるため、vendor がなくても正常です。


確認してほしいこと

次のファイルがあるか教えてください。

config/importmap.rb

このファイルが

  • ある → Importmap を使っています。
  • ない → esbuild や Vite の可能性が高いです。

また、次のコマンドの結果も分かると判断できます。

bin/rails -v

ls app/javascript

または、プロジェクトのディレクトリ構成(app/javascriptconfig の中身)が分かれば、次に何をすればよいか具体的に案内できます。

これは正常なメッセージです。

bin/rails importmap:install を実行すると、Importmap に必要なファイルを作成しようとしますが、あなたのプロジェクトには既に

app/javascript/application.js

が存在するため、

conflict app/javascript/application.js
Overwrite .../app/javascript/application.js?
[Ynaqdhm]

と確認してきています。

それぞれの意味は、

  • Y = 上書きする
  • n = 上書きしない(おすすめ)
  • a = 以降すべて上書き
  • q = 中止
  • d = 差分を見る
  • h = ヘルプ

です。

今回はどうする?

n を入力して Enter をおすすめします。

理由は、あなたは既に application.js を使っているので、上書きすると現在の設定が消えてしまう可能性があるためです。


その後、確認したいこと

n を押して最後まで実行したら、次の2つを教えてください。

  1. vendor/javascript フォルダは作成されましたか?
  2. 次のコマンドの結果を貼ってください。
ls -R vendor

もし

ls: cannot access 'vendor': No such file or directory

と表示された場合は、そのメッセージもそのまま貼ってください。

そこまで確認すれば、次に何をすべきか判断できます。

/home/hiroppy123/tweet_app005/config/importmap.rb? (enter "h" for help) [Ynaqdhm]

ここも同じく確認メッセージです。

今回は、すでに

config/importmap.rb

が存在しているため、

Overwrite .../config/importmap.rb?
[Ynaqdhm]

と聞かれています。

おすすめ

ここも n を入力して Enter してください。

理由は、既存の config/importmap.rb を上書きすると、現在設定されている pin の内容が消える可能性があるからです。


良いニュース

先ほどの出力を見ると、

create vendor/javascript
create vendor/javascript/.keep

となっているので、

vendor/javascript は正常に作成されています。

つまり、「vendor フォルダがない」という問題は解決しています。


次に知りたいこと

n を押してインストールが最後まで終わったら、次の3つの内容を見せてください。

config/importmap.rb

# 全部

ls app/javascript/controllers

の結果

bin/importmap json

の結果

この3つが分かれば、theme_controller.js が正しく読み込まれる状態になっているかを確認できます。

body { background: var(--body-bg); color: var(--text-color); }       application.css しか、ない場合も、これは、必要ですか?

はい、必要です。

ただし、書く場所は application.css ではなくても構いません。


理由

あなたの theme.scss では、

:root {
--body-bg: #3ecdc6;
--text-color: #2d3133;
}

というように、CSS変数を定義しているだけです。

変数を定義しただけでは、画面の色は変わりません。

実際にどこかで

body {
background: var(--body-bg);
color: var(--text-color);
}

のように変数を使う必要があります。


例えば

もし今、

body {
background: #3ecdc6;
color: #2d3133;
}

となっているなら、

これを

body {
background: var(--body-bg);
color: var(--text-color);
}

に変更します。

これで、

document.documentElement.setAttribute("data-theme", "dark")

が実行されると、

[data-theme="dark"] {
--body-bg: #0f1720;
--text-color: #e7e9ea;
}

が適用され、背景色や文字色が切り替わります。


application.css しかない場合

もし

app/assets/stylesheets/
application.css
theme.scss

という構成なら、

application.csstheme.scss を読み込んでいれば、body { ... }どちらに書いても構いません

例えば

application.css

/*
*= require_tree .
*= require_self
*/

theme.scss

body {
background: var(--body-bg);
color: var(--text-color);
}

でも動きます。


確認したいこと

一つ確認したいのですが、現在の application.css の内容をそのまま貼っていただけますか?

特に最初の20行くらいが分かれば、theme.scss が読み込まれる設定になっているかを確認できます。

広告

ありがとうございます。ログを見ると、テーマ切り替えより前に、JavaScript全体が止まっています。

一番重要なのは最後のエラーです。

Uncaught SyntaxError:
The requested module 'jquery'
does not provide an export named 'default'

このエラーが出ると、theme_controller.js を含む後続のJavaScriptが実行されない可能性があります。


まず直すべきなのはこれです

見せていただきたいファイルがあります。

app/javascript/application.js

の内容をそのまま貼ってください。

特に、おそらくこのような行があるはずです。

import $ from "jquery"

または

import jquery from "jquery"

この書き方が原因の可能性があります。


次に気になる点

ログには

GET /@hotwired--turbo.js
No route matches

GET /@rails--actioncable--src.js
No route matches

も出ています。

これは Importmap の設定と application.html.erb の読み込み方が一致していない可能性があります。


見せていただきたいもの

次の3つを貼っていただければ、原因を特定できます。

  1. app/javascript/application.js
  2. config/importmap.rb
  3. app/views/layouts/application.html.erb<head> 部分(javascript_importmap_tags があるあたり)

現時点の結論

テーマ切り替えのコード自体が原因ではありません。

今は

  • ❌ JavaScript の読み込み(jquery
  • ❌ Importmap の設定(@hotwired--turbo.js など)

の2点に問題があり、その影響で theme_controller.js が実行されていない状態です。

この2つを直せば、テーマ切り替えも動く可能性が高いです。

ありがとうございます。原因がかなり見えてきました。

config/importmap.rb に問題があります。


@hotwired/turbo-rails が二重に定義されています

現在は

pin "@hotwired/turbo-rails", to: "@hotwired--turbo-rails.js"
pin "@hotwired/turbo-rails", to: "turbo.min.js"

となっています。

これは どちらか一つだけにしてください。

通常は

pin "@hotwired/turbo-rails", to: "turbo.min.js"

だけで十分です。


@hotwired/turbo の設定がおかしい

現在

pin "@hotwired/turbo", to: "@hotwired--turbo.js"

となっていますが、

ログに

GET /@hotwired--turbo.js
No route matches

と出ています。

つまり

@hotwired--turbo.js

というファイルが存在しません。


③ jQuery のエラー

一番重要なのは

import $ from 'jquery';

です。

Importmap版の jQuery は

import $ from "jquery"

では読み込めないことがあります。

だから

The requested module 'jquery'
does not provide an export named 'default'

というエラーになります。

試しに

import $ from "jquery";

を一旦コメントアウトしてください。

// import $ from "jquery";

さらに

window.$ = $

などを書いているなら、それもコメントアウトしてください。

まずはテーマ切替が動く状態にしましょう。


④ application.js

今は

import { Application } from "@hotwired/stimulus";
const application = Application.start();

となっていますが、

通常は

import "controllers"

だけでStimulusは起動します。

つまり

const application = Application.start();

は不要です。


私がおすすめする application.js

import "@hotwired/turbo-rails"
import "@rails/ujs"
import "controllers"
import Rails from "@rails/ujs"
Rails.start()

これで十分です。


最後に確認したいこと

config/importmap.rb を全部貼ってください。

今の内容を見ると、

  • Turbo
  • Stimulus
  • ActionCable

の設定が少し混ざってしまっています。

config/importmap.rb 全体を見れば、「どの行を削除して、どの行を残すべきか」を具体的にお伝えできます。

ありがとうございます。かなり原因が絞れました。

theme_controller.js や HTML はほぼ問題ありません。

今、一番怪しいのは application.html.erb<head> の設定です。

あなたが貼ってくださったのは <header> の部分で、私が確認したいのは <head> の部分です。

例えば、このような部分です。

<!DOCTYPE html>
<html>
<head>
<title>TweetApp</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>

または

<%= javascript_include_tag ... %>

などが書かれている部分です。


今の時点で一つ気になること

あなたの application.js

import "@hotwired/turbo-rails"
import Rails from "@rails/ujs"
import "controllers"
Rails.start()

のようにするのが正しいです。

一方で、

GET /@hotwired--turbo.js

というエラーが出ていました。

これは javascript_importmap_tags が正しく読み込まれていないか、Importmap の設定が食い違っている可能性があります。


次に見せてください

app/views/layouts/application.html.erb<head> 全体

つまり、

<!DOCTYPE html>
<html>
<head>
...
</head>

までを貼ってください。


もう一つ確認したいこと

ターミナルで次のコマンドを実行して、その結果も貼ってください。

bin/importmap json

これで、Importmap が実際にどの JavaScript を読み込もうとしているかが分かります。

この2つ(<head>bin/importmap json)が分かれば、@hotwired--turbo.js エラーの原因をかなり正確に特定できます。

<head> <title>TweetApp</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <!-- Bootstrap CSSはSass経由でapplication.cssにバンドルされるため、 CDNからの二重読み込みはやめました。Font Awesomeのみ引き続きCDNで読み込みます --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> <%= stylesheet_link_tag 'application', 'data-turbo-track': 'reload' %> <%= javascript_importmap_tags %> <link rel="shortcut icon" href="/favicon.png" /> <!-- ページ描画前にテーマを適用し、切り替え時のチラつき(FOUC)を防ぐ --> <script> (function () { var theme = localStorage.getItem('theme') || 'normal'; document.documentElement.setAttribute('data-theme', theme); })(); </script> </head>

ありがとうございます。application.html.erb<head>問題ありません

特にここ:

<%= javascript_importmap_tags %>

があるので、Importmapの読み込み自体は正しいです。

現在の問題は、ほぼ config/importmap.rbapplication.js の整理不足です。


1. application.js を修正してください

現在の状態では、

Rails.start();
application.debug = true;
window.Stimulus = application;
export { application };

の部分が問題です。

application という変数は、このファイル内では定義していません。

以下に変更してください。

// app/javascript/application.js
import "@hotwired/turbo-rails"
import "controllers"

まずはこれだけで動作確認してください。

理由:

  • Turbo → @hotwired/turbo-rails が読み込む
  • Stimulus → import "controllers" が読み込む
  • controllers/application.js が Stimulus を起動する

というRails 7/8 Importmap標準構成になります。


2. config/importmap.rb を整理してください

現在:

pin "jquery"
pin "@rails/ujs"
pin "@hotwired/stimulus"
pin "@hotwired/turbo"
pin "@rails/actioncable/src"
pin "@hotwired/turbo-rails"

となっていますが、不要なものが混ざっています。

まずテーマ切替を動かす目的なら、以下くらいで十分です。

pin "application", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/turbo-rails", to: "turbo.min.js"

削除候補:

pin "jquery"
pin "@rails/ujs"
pin "@hotwired/turbo"
pin "@rails/actioncable/src"

3. controllers/application.js はそのままでOKです

これは正常です。

import { Application } from "@hotwired/stimulus"
const application = Application.start()
application.debug = false
window.Stimulus = application
export { application }

問題ありません。


4. その後再起動

Railsサーバーを停止:

Ctrl + C

キャッシュを削除:

bin/rails tmp:clear

再起動:

rails s -b 127.0.0.1 -p 4003

5. 確認

ブラウザのConsoleで、

Stimulus

と入力して、

Application

のようなオブジェクトが表示されればOKです。

また、テーマボタンを押した時に <html>

<html data-theme="dark">

のように変化すれば、theme_controller.js は正常動作しています。


今回の流れを見る限り、theme.scsstheme_controller.js はほぼ完成しています。
残っている問題は 古いRails UJS + jQuery + Turbo設定が混在していることです。

application-e693eb5236d201a1264a8d539e3a84b794f407bcf7cad9b71ec14eab4fbb82a7.js:10 Uncaught ReferenceError: Rails is not defined at application-e693eb5236d201a1264a8d539e3a84b794f407bcf7cad9b71ec14eab4fbb82a7.js:10:1 (anonymous) @ application-e693eb5236d201a1264a8d539e3a84b794f407bcf7cad9b71ec14eab4fbb82a7.js:10 theme_controller-6b01b0587a5086480af2d2848cf8700e6983c66ec648f1039c083224960f324c.js:13      ーーーーー     テーマ、反映成功ですが、エラーの、様な物が、

状況を見る限り、テーマ切り替えは成功しています。
つまり、

theme_controller-xxxxx.js

が読み込まれて、data-theme="dark" などの切り替えが動いています。

残っているエラーは別件です。


エラー原因

表示されている:

Uncaught ReferenceError: Rails is not defined
at application.js:10

これは application.js にまだ

Rails.start();

が残っているためです。

現在のImportmap構成では、

import "@rails/ujs"

を削除した場合、

Rails

という変数は存在しません。


修正

app/javascript/application.js

を以下にしてください。

import "@hotwired/turbo-rails"
import "controllers"

これだけでOKです。

削除するもの:

Rails.start();
import Rails from "@rails/ujs"

なぜテーマは動いたのか?

流れは現在:

application.html.erb
javascript_importmap_tags
application.js
import "controllers"
controllers/index.js
theme_controller.js
テーマ切替

になっています。

なので、Rails.start() のエラーが出ても、Stimulus部分はすでに読み込まれて動いています。


修正後

Railsサーバー再起動:

Ctrl + C

rails s -b 127.0.0.1 -p 4003

ブラウザを

Ctrl + Shift + R

で強制リロードしてください。


補足

theme_controller.js のこのログ:

theme_controller-6b01....js:13

が出ているので、

✅ Stimulus読込
✅ Controller認識
✅ ボタンイベント
✅ CSS変数切替

ここまでは正常です。

あとは Rails is not defined を消せば、現在の構成はかなりきれいな Rails 8 + Importmap + Stimulus 構成になります。