Toggle Between Light and Dark Mode?

Hello! It’s been years since I’ve properly used CSS and HTML, and I’ve barely touched Javascript before, so I’m pretty rusty on the easy things.

I’m trying to have a button in my sidebar that, when pressed, will toggle between light mode/dark mode, essentially to invert the body’s text color and background color. I’ve assigned each of those to a variable in :root, so they’re easy to access; I just don’t know how to actually go about making a function to swap between two themes on a button press. I know it’s something-something-Javascript, but that’s about all I’ve got.

I’ve tried to work with the Sugarcube Setting.API, but can’t seem to wrap my head around it – I’ve gathered that I’ll be needing to make two seperate body classes in my stylesheet and find a way to swap between them.

Any help would be appreciated for this rookie! Thanks!

2 Likes

One way to do it would be to make a swapped set of variables under a different selector.

:root {
    /* variables, default (light) versions */
}

[data-theme="dark"] {
    /* dark theme variables */
}

And then set data-theme="dark" on the root element with JavaScript like so:

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

(and then maybe set it to “light” or some other value when you toggle it the other way)

Hook it up to the click event on the button with addEventListener, and probably use document.documentElement.getAttribute("data-theme") to see which state it’s currently in.

I don’t know if that’s enough to get you started, or if more details would be helpful…?

1 Like

Well, stubborness won out and I wound up trying to cobble something together with good ol’ W3Schools samples. While it may not be working, I think I’m closer…? Here’s where I’m at.

Stylesheet:

body {
  font-family: "Open Sans", sans-serif;
  font-size: 16px; 
  color: var(--text-color);
  background-color: var(--background-color);
}

.dark-mode {
   font-family: "Open Sans", sans-serif;
  font-size: 16px; 
  color: #e4e4e4;
  background-color: #262223;
}

JS:

function myFunction() {
   var element = document.body;
   element.classList.toggle("dark-mode");
}

Yes, terrible naming I know, but I’ll change it once it works.

HTML: (Currently just in StoryCaption)

<button onclick="myFunction()">Toggle dark mode</button>

Yet, when the button is clicked, it says ‘myFunction()’ is not defined.

Any advice or alternatives?

1 Like

makar, that is more-or-less the approach I used for QuestJS. All the real work is done in CSS. It does make the style sheet more complicated if there are a lot of changes, but still the easiest way I suspect. If you need to have other elements change, do something like this:

.input-text {
  font-style:italic;
  color:green;
}
.dark-mode  .input-text {
  color:lightgreen;
}

The first bit sets the default for my class input-text. The second gets used when the body element (or any parent element) has the dark-mode class - so no need to change the JavaScript.

1 Like

Hmm…I always forget the details of visibility in SugarCube/JavaScript. You could try window.myFunction = function() { ...etc... }.


Most people don’t use the onclick attribute these days, which for a single-developer project is largely just a fashion trend, but the more current thing would be to give the button an ID: <button id="toggle-theme"> and then add the event handler with javascript, maybe like:

$(document).on(':storyready', $('#toggle-theme').on('click', myFunction))

Aha! It was that “window.myFunction” line that fixed it!

Thanks for all the help!

EDIT:

Whoops, got wildly ahead of myself. The switch works perfectly… but only on the current passage before it reverts back to the default light mode.

Here’s how to do it with the Setting API:

var settingDarkHandler = function () {
		if (settings.darkMode) {
				$("html").addClass("dark");
		}
		else {
				$("html).removeClass("dark");
		}
};

Setting.addToggle("darkMode", {
     label    : "Enable dark mode.",
     default  : false,
     onInit   : settingDarkHandler,
     onChange : settingDarkHandler
});

With corresponding “.dark” CSS in your stylesheet.

2 Likes

Even if it’s a bit more cumbersome, I’m favoring the simple toggle button on the sidebar rather than another submenu.

I’m thinking that the function, as-is, works. There’d just need to be a check in passageHeader – if, say, a boolean var named lightMode is false, then run “myFunction”. The button’s onClick would set the bool lightMode to the opposite of whatever it is (I think that’d be something like lightmode = !lightMode ?).

I just… admittedly, have no clue how to write that actual code and implement it. Would someone be able to adapt the pseudocode into something functional?

Using special passages that way is not encouraged, although I will admit that knowing that it’s not best practice has not stopped me from doing this exact thing in the past.

But if you really want to do it this way, you can declare the variable $lightMode in StoryInit and then put the following in your StoryMenu passage (or StoryCaption as you currently have it, but this is the intended use of StoryMenu):

<<link "Dark Mode">>
	<<set $lightMode = ! $lightMode>>
	<<run Engine.show()>>
<</link>>

(Using Engine.show is what makes the changes take effect immediately as opposed to only showing up when you go to the next passage.)

And then put this in your PassageHeader:

<<if $lightMode === false>>
	<<script>>	
		myFunction();
	<</script>>
<</if>>
1 Like

you’re a saint

It works perfectly, and fits my UI wonderfully. Thank you so much!

I might try to make a code template with this system just to help others, so thank you so much for helping out!

2 Likes

It really wasn’t a big effort—like I said, I’ve done dark mode this way myself, so I only had to tweak my own code/markup a little. Glad it was helpful!

(If anyone has a single-click/no-menu solution that doesn’t involve unrecommended hacky use of special passages, though, I would also love to know about it!)

I’m curious why you’re using the State.variables property to reference a Story variable in a Macro call.

eg. why you didn’t write…

<<set $lightMode = ! $lightMode>>
1 Like

Whoops, sorry, that’s an artifact of me messing around a bit trying to do the whole thing in JS (just because I had the impression that that was what OP wanted) before deciding that there was no need to reinvent the wheel. Thanks for pointing it out—I’ll edit my comment.

Please don’t dredge up SugarCube v1 APIs—that have been deprecated for years. Let them die in peace.

Try:

<<link "Dark Mode">>
	<<set $lightMode = !$lightMode>>
	<<script>>
		Engine.show();
	<</script>>
<</link>>

Or:

<<link "Dark Mode">>
	<<set $lightMode = !$lightMode>>
	<<run Engine.show()>>
<</link>>
1 Like

Yeah, sorry, I’m an idiot and didn’t know about that. I’ll edit the post again and be more careful about this in the future.