Skip to content

Lazy TailwindCSS Theme Switching

Published: at 03:34 PM

I just wanted to quickly document the steps I took to make a theme toggle for a client.

Note: This is just one of many methods to do this, and I just found my task a lot easier once I implimented a few intersting tools.

βœ” Watch this Video and Understand the Implications of Dark Mode Color Selections.

Be sure to plan for dark mode first. Also, watch anything and everything that Juxtopposed puts out. Totally worth it!

Plan for Dark Mode first!

βœ” Get your Realtime Colors On

Head over to Realtime Colors, from Juxtopposed and hit dark mode. Plan and swap back and forth till you are happy.

If you need help, use this video.

βœ” Grab your TailwindCSS vars from Realtime Colors

  1. Place variables in a global.css file.
// Random Color Selection just for example
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --text: #101823;
    --background: #f7f7f7;
    --primary: #cd71f4;
    --secondary: #fb9518;
    --accent: #f27d7d;
  }
  .dark {
    --text: #dce4ef;
    --background: #080808;
    --primary: #670b8e;
    --secondary: #e78104;
    --accent: #800d0d;
  }
}
  1. Modify tailwind.config.cjs to use the variables. Seeting the darkMode: class export, it makes it very easy to simply swap themes based on CSS toggling with minimal JS.
// Ensure this content is present

module.exports = {
	// ...
	darkMode: 'class',
	theme: {
		fontSize: {
			sm: '0.750rem',
			base: '1rem',
			xl: '1.333rem',
			'2xl': '1.777rem',
			'3xl': '2.369rem',
			'4xl': '3.158rem',
			'5xl': '4.210rem'
		},
		colors: {
			text: 'var(--text)',
			background: 'var(--background)',
			primary: 'var(--primary)',
			secondary: 'var(--secondary)',
			accent: 'var(--accent)'
		},
	// ...
};
  1. Create Toggle components
<div class="flex w-[70px] items-center justify-end">
  <label class="relative inline-block h-[34px] w-[65px]" for="checkbox">
    <input class="hidden" type="checkbox" id="checkbox" />
    <div
      class="slider bg-accent checked:bg-accent absolute bottom-0 left-0 right-0 top-0 cursor-pointer rounded-full transition-transform"
    >
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="16"
        height="16"
        viewBox="0 0 24 24"
        fill="var(--background)"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="lightsun bottom-[9px] right-[9px] translate-x-0 opacity-100"
      >
        <circle cx="12" cy="12" r="5"></circle>
        <line x1="12" y1="1" x2="12" y2="3"></line>
        <line x1="12" y1="21" x2="12" y2="23"></line>
        <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
        <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
        <line x1="1" y1="12" x2="3" y2="12"></line>
        <line x1="21" y1="12" x2="23" y2="12"></line>
        <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
        <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
      </svg>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="16"
        height="16"
        viewBox="0 0 24 24"
        fill="var(--background)"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="darkmoon bottom-[9px] left-[9px] translate-x-[4px] opacity-0"
      >
        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
      </svg>
    </div>
  </label>
</div>

<style>
  /* I'm still working on shifting everything to tailwind, but at least it's here!  */

  .slider:before {
    background-color: var(--background);
    bottom: 4px;
    content: "";
    height: 26px;
    left: 4px;
    position: absolute;
    transition: 0.4s;
    width: 26px;
    border-radius: 50%;
  }

  input:checked + .slider:before {
    transform: translateX(26px);
  }

  .slider svg {
    color: var(--text);
    position: absolute;
    transition:
      opacity 0.2s ease 0s,
      transform 0.35s ease 0s;
    pointer-events: none;
  }

  input:checked + .slider .darkmoon {
    opacity: 1;
    transform: translateX(0);
  }

  input:checked + .slider .lightsun {
    opacity: 0;
    transform: translateX(-4px);
  }
</style>

Place the code below in a <script> tag in the .astro component.

const themeToggle = document.querySelector('input[type="checkbox"]');
const currentTheme = localStorage.getItem("theme");

if (currentTheme) {
  document.documentElement.classList.add(currentTheme);

  if (currentTheme === "dark") {
    themeToggle.checked = true;
  }
}

function switchTheme(e) {
  if (e.target.checked) {
    document.documentElement.classList.add("dark");
    document.documentElement.classList.remove("light");
    localStorage.setItem("theme", "dark");
  } else {
    document.documentElement.classList.add("light");
    document.documentElement.classList.remove("dark");
    localStorage.setItem("theme", "light");
  }
}

themeToggle.addEventListener("change", switchTheme);

Import the component where ya want and enjoy!

πŸš€ Explaination

By having the variables set in the global.css file, it’s insanely easy to have all the values for the variables change simply by adding the class dark to the html DOM element.