GJ
Coding

Light and Dark theme toggle on a Laravel website

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.

CSS

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.

Javascript

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';

toggle_icon.addEventListener('click', function() {
    if (toggle_icon.classList.contains(sun_class)) {
        toggle_icon.classList.add(moon_class);
        toggle_icon.classList.remove(sun_class);

        body.classList.remove('dark-theme');

        setCookie('theme', 'light');
    }
    else {
        toggle_icon.classList.add(sun_class);
        toggle_icon.classList.remove(moon_class);

        body.classList.add('dark-theme');

        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.

PHP & Laravel

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\GJames\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.

HTML

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' }}">

Summary

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.