While creating my portfolio I found the need to implement a dark mode with no more than a few tools, because this project was made to learn ASP.NET, not a javascript framework. So go on...
Tools
- C#
- CSS
- JavaScript
Some background
When I was researching about how to make a dark mode I found this website. I followed up the "body-class" method, all worked fine but when I refresh the page the theme doesn't persist. Then, I tried to save data to local storage with JavaScript, but it didn't work, so I decided to use cookies.
Method 1 - LocalStorage
In this way, we keep the changes of the view on the client-side.
Step 1
Add a class to the components that you want to change their theme. This class will be your default theme, so keep it in mind.
// Shared/_Layout.cshtml
<body class="dark-theme">
@RenderBody()
<script src="~/js/site.js" asp-append-version="true"></script>
</body>
// Views/Home
<div class="home-lower dark-theme">
</div>
Step 2
Add a script to remove the class name based on the local storage variable name.
const themeName = 'dark-theme';
const itemDivs = document.querySelectorAll('.dark-theme');
const themeSwitcher = document.getElementById('theme-switcher');
const darkTheme = localStorage.getItem(themeName);
if(darkTheme === null){
localStorage.setItem(themeName,"active");
} else if(darkTheme === "active"){
addTheme();
} else {
removeTheme();
}
themeSwitcher.addEventListener('click',function (){
let currentTheme = localStorage.getItem(themeName);
if(currentTheme === "inactive"){
localStorage.setItem(themeName,"active");
addTheme();
} else if (currentTheme === "active"){
localStorage.setItem(themeName,"inactive");
removeTheme();
}
})
function removeTheme(){
itemDivs.forEach(itemDiv => {
itemDiv.classList.remove(themeName);
});
}
function addTheme(){
itemDivs.forEach(itemDiv => {
itemDiv.classList.add(themeName);
});
}
Step 3
Add a button with an id to be listened by the event from the script (tutorial)
<button class="btn-toggle" id="theme-switcher"></button>
Step 4
Add your css rules to change your colors based on class name
body {
background-color: white;
}
body.dark-theme {
background-color: gray;
}
Method 2 - Cookies
This way, you need to call an action in the controller to make a new request to the server, but you will need to update the DOM with Javascript (Looking back, it sounds terrible to separate the logic between client and server).
HTTP Context
Reading about the request pipeline, we can find HttpContext class, which describes request and response information. In this case, we need to read the user request available through the Request member.
public abstract class HttpContext
{
public abstract HttpRequest Request { get; }
}
And what do we need to know about the request? 🍪🍪🍪
Cookies
We will use cookies to store our dark theme information in something like ["dark-theme" = true]
and it is available through the Cookies request member.
public abstract class HttpRequest
{
public abstract IRequestCookieCollection Cookies { get; set; }
}
Executing request
As I tried to avoid using javascript except to handle the DOM, we will use an Action from our Controller to check if the user is requesting dark or light theme. Let's go
How it works?
The theme changer will be executed on the Home view of my website. It will trigger another action method from the HomeController and redirect to the Index action of that controller.
Step 1
Add a class to the components that you want to change their theme. This class will be your default theme, so keep it in mind.
// Shared/_Layout.cshtml
<body class="dark-theme">
@RenderBody()
<script src="~/js/site.js" asp-append-version="true"></script>
</body>
// Views/Home
<div class="home-lower dark-theme">
</div>
Step 2
Add a script to remove the class name based on the cookie name. I use the readCookie method from a Quirksmode post and the script from ChatGPT because is not a technology that I handle.
⚠️ It's important that your template runs javascript on the pages where you will use the method to change the theme, that's why I used the base layout from ASP.NET
// wwwroot/js/site.js
const darkTheme = 'dark-theme'; // Your class name
const itemDivs = document.querySelectorAll('.dark-theme');
// If dark theme is active, remove class names
if(readCookie(darkTheme) === 'true'){
itemDivs.forEach(itemDiv => {
itemDiv.classList.remove(darkTheme);
});
}
function readCookie(name) {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for(let i=0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0)===' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
Step 3
Add a button to execute an action method called ToggleTheme in your HomeController
<div>
<a asp-action="ToggleTheme" asp-controller="Home">
</div>
Step 4
Add a method to set a default cookie called as your theming class
(dark-theme == false equals to "keep class name", and yeah, it is bad, do it better)
public class HomeController : Controller
{
public IActionResult Index()
{
var requestCookie = HttpContext.Request.Cookies;
var response = HttpContext.Response.Cookies;
if (!requestCookie.ContainsKey("dark-theme"))
{
response.Append("dark-theme","false");
}
return View("Home");
}
}
Add a method to change your cookie value based on user request and redirect to index because when you return the view your url will be like /Home/ToggleTheme
public IActionResult ToggleTheme()
{
var requestCookies = HttpContext.Request.Cookies;
var responseCookies = HttpContext.Response.Cookies;
if (requestCookies.ContainsKey("dark-theme") && requestCookies["dark-theme"]!.Contains("false"))
{
responseCookies.Append("dark-theme","true");
}
else
{
responseCookies.Append("dark-theme","false");
}
return RedirectToAction("Index");
}
Step 5
Add your css rules to change your colors based on class name
body {
background-color: white;
}
body.dark-theme {
background-color: gray;
}
That's all
If you have any corrections I look forward to your comments, I didn't find much information on StackOverflow or Youtube so I made it with what I know. I hope I haven't forgotten anything
Code: https://gitlab.com/vlank/portfolio
Greetings from 🇦🇷
Top comments (3)
This is bad advice, please read about prefers-color-scheme media feature.
I am interested in hearing why ** Then, I tried to save data to local storage with JavaScript, but it didn't work** didn’t work
Usually one should follow the prefers-color-scheme first and have a .dark class, in the body element, that overrides the browser when changed manually.
Roughly speaking, this feature would be useful if you wanted to detect the user's preferred theme? (But if I want to keep my default theme and allow the user to change it, it would not... maybe).
And about local storage, I made a mistake while testing and testing things, because it works keeping the same approach. I will rewrite these things later
Edit: I think one of the reasons I didn't use local storage was because I wanted to keep that logic server-side and handled by C#
The consensus around how to implement dark theme is to adhere to the OS preferences first, most, if there’s a slider available, will not change it for every website they go to & you might as well not add dark mode altogether.