I find using a dark theme for my IDE and operating system is much easier on my eyes, so I use it wherever possible. In this post I explain how to create a toggle button to switch a Laravel website between light and dark themes.
We’ll achieve this by adding a button to the page that will call a Javascript function that adds or removes a class from our body
tag and sets a cookie with our preference. The back end code will know to look for this cookie and will set the body class when it builds the page. This persistence is done in the back end rather than by the Javascript so that there’s no flash between styles when the Javascript is loaded.
I’ll keep the CSS simple so it doesn’t detract from the main point of this post. Let’s assume you have the following basic CSS for a light theme on a website. It’s simply just black text on a white background.
body {
color: black;
background-color: white;
}
A simple “dark theme” could be to just invert the colours so that we have white text on a black background. We’ll put these in a .dark-theme
class.
.dark-theme {
color: white;
background-color: black;
}
Your styles will obviously be more complicated than this. I write my styles in SCSS and use Laravel Mix to compile them to CSS. This makes it much easier to wrap all your dark theme styles in a parent class.
Next, we need some Javascript that will allow us to click an element on the page to toggle between the two themes.
The following Javascript attaches a click
event to an element with the ID theme-toggle
and toggles between two classes on that element to show a sun icon or a moon icon (a moon is shown when the light theme is active, and a sun when the dark theme is active). It also adds or removes the dark-theme
class from the body element.
Lastly, it sets a cookie called “theme” that holds the value “light” or “dark”.
var toggle_icon = document.getElementById('theme-toggle');
var body = document.getElementsByTagName('body')[0];
var sun_class = 'icon-sun';
var moon_class = 'icon-moon';
var dark_theme_class = 'dark-theme';
toggle_icon.addEventListener('click', function() {
if (body.classList.contains(dark_theme_class)) {
toggle_icon.classList.add(moon_class);
toggle_icon.classList.remove(sun_class);
body.classList.remove(dark_theme_class);
setCookie('theme', 'light');
}
else {
toggle_icon.classList.add(sun_class);
toggle_icon.classList.remove(moon_class);
body.classList.add(dark_theme_class);
setCookie('theme', 'dark');
}
});
function setCookie(name, value) {
var d = new Date();
d.setTime(d.getTime() + (365*24*60*60*1000));
var expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
}
We’re checking for classes here rather than holding the active theme in a variable because the code in the view will set this class (as you’ll see below) and so it’s not really necessary to set a variable there too.
In our back end code, we need to get the value of the theme cookie and make it available to our master blade layout.
Firstly, since the cookie was set by Javascript, we didn’t encrypt it and Laravel can, by default, only see cookies encrypted by Laravel. We can specify exceptions in the App\Http\Middleware\EncryptCookies
class.
protected $except = [
'theme'
];
Now that Laravel can read the cookie, we can use a view composer to make it’s value available to the master layout.
I’ve placed the following code in the boot()
function of the App\Providers\AppServiceProvider
class. For a small application this is fine but you may want to create your own dedicated service provider for this if you’re working on a larger application.
view()->composer('layouts.master', function ($view) {
$theme = \Cookie::get('theme');
if ($theme != 'dark' && $theme != 'light') {
$theme = 'light';
}
$view->with('theme', $theme);
});
What a view composer does is runs some code whenever a specific view is loaded. In this case we’re looking at the layouts.master
view. We use the Cookie
facade to check the value of the “theme” cookie. If it’s anything but “light” or “dark” we default to “light” and then use the with(name, value)
function to make a theme
variable available to the view.
Last of all, we need to set the relevant classes in our view. As mentioned previously, we could let our Javascript set this class, but then there would be a flash of the default styling displayed on the page whilst the Javascript was loaded and evaluated. By setting it in the back end it’s available straight away when it reaches the browser.
I’m using blade templates in my Laravel project. The two snippets of code below are in my master layout file. The $theme
variable was set using a view composer in the AppServiceProvider
as mentioned above.
First the element that I’m clicking to toggle the theme:
<i id="theme-toggle" class="icon-{{ $theme == 'dark' ? 'sun' : 'moon' }}"></i>
And this is where I add the class to the body
element:
<body class="{{ $theme . '-theme' }}">
That’s all there is to it! We’ve successfully set a cookie and applied some styles using Javascript to toggle the theme. We’ve then utilised that cookie in our back end code to set the relevant classes when the page is generated for subsequent page loads.
comments powered by Disqus