Dark mode toggle in Django with TailwindCSS and Alpine.js

Nowadays all modern websites are providing an option to change a default design into its dark mode version. Somehow this trend becomes a standard and there are several approaches how to achieve this behavior.

  • calendar_month 7.7.2023
  • pace 10 minutes

Below we will look at the basic implementation in Django without using any Python code (just templates) in combination with Tailwind CSS and Alpine.js. Tailwind CSS will provide a visual style for the default design and dark move version. Alpine.js will be responsible for the dynamic part of switching between these two modes.

Tailwind CSS configuration

First of all, let's start with the basic configuration of Tailwind CSS. The prerequisite is that tailwind.config.js file is already available with appropriate configuration options. Everything that is needed to do now, is to add darkMode option. This will tell Tailwind to switch the styles based on dark CSS class, in our cases available on body element.

// tailwind.config.js

module.exports = {
  darkMode: "class",
};
// templates/skeleton.html

{% load static %}

<html>
  <head>
    <link href="{% static 'css/styles.css' %}" rel="stylesheet">
  </head>

  <body>
    {% block content %}{% endblock }
  </body>
</html>

There is one more additional thing, which is required to be added into custom Tailwind styles. When the page is loaded, there is short blib of different styles before. It is visible when the dark mode is enabled but for the short period of time original theme is display. This can be fixed be creating selector below and then applying it on body element.

/* styles.css */

[x-cloak] {
  @apply !hidden;
}

Alpine.js configuration

For the dynamic part of the application, it is necessary to load Alpine.js library. In this example, CDN version is going to be good enough but in your custom application, the script can be downloaded into static/ folder and then loaded via {% static %} template tag.

On top of that, one additional Alpine.js library is required which is going to be responsible for persisting current theme selection into local storage. Below you can see the new version of html element, adding these libraries.

<head>
  <link href="{% static 'css/styles.css' %}" rel="stylesheet">
  <script
    defer
    src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"
  ></script>
  <script
    defer
    src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
  ></script>
</head>

Swithing between themes

The currently implemented switcher is going to have three available options. The first option is the default light mode version of the website, the second option is the dark mode version. The third version is "auto", based on current system settings default theme is going to be selected, it can be light or dark.

Our application, as was already mentioned, is going to store this information in local storage by using Persist, Alpine.js library. The selected option is available in local storage, under theme option.

For the whole implementation, just three Alpine.js directives are used. The simplest one is x-cloak, which will hide website until everything is properly loaded.

Then for initializing the data directive x-data has to be set to { theme: $persist('auto').as('theme') }. The value is initializing theme variable from local storage and in case that key does not exist, value auto is used.

The last directive is setting proper Tailwind CSS class to html element. For this purpose x-bind directive will set proper class attribute base on theme variable, initialized in previous step via x-data directive.

// skeleton.html

<html
  x-cloak
  x-data="{ theme: $persist('auto').as('theme') }"
  x-bind:class="{
    'dark': theme === 'dark' || 
    (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)
  }"
></html>

The last step is to code a toggle navigation inside Django template. For this purpose simple nav element with links is enough. On each link it is necessary to attach click event by using x-on directive. Clicking on link will change value of theme variable. As this variable is coming from persist plugin, it will push value into local storage as well causing that after the page refresh, value will be not lost.

// base.html

{% extends "skeleton.html" %}

{% block content %}
  <nav>
    <a x-on:click="theme = 'dark'">Dark</a>

    <a x-on:click="theme = 'light'">Light</a>

    <a x-on:click="theme = 'auto'">System</a>
  </nav>
{% endblock %}

Let's work together

Did you decide to start using Unfold in your application but you need help with integration? Feel free to get in touch for consulting or development services.

  • verified Save time and focus on what matters: building product
  • verified Get instant access to the know-how pool and experience
  • verified Supercharge your development processes and quality of application
  • verified Incorporate custom Django applications into Unfold

* The cost of service starts from €375 EUR.

© 2024 Created by unfoldadmin.com. All rights reserved.