Creating a Sleek Theme Switcher Using Tailwind

There are countless ways to style a theme switcher. Some prefer a button, while others prefer a switch. In some cases, developers rely on the client's system settings alone with no UI at all.

In this article, we will try to make a simple button that switches between icons based on the selected theme - similar to what I use on my navbar up top. We'll then give it a nice rotate transition to give that sleek look, all without writing any CSS thanks to Tailwind.

This is my first article written in MDX and I'm pretty excited. Let's dive into it!

Our End Goal

The Code

The simplified code below assumes that the component receives an onClick function that toggles the theme. The icons are from HeroIcons, but feel free to use your own.

ThemeSwitcher.js
import { MoonIcon, SunIcon } from '@heroicons/react/24/solid'
 
export default function ThemeSwitcher({ onClick }) {
  return (
    <button
      className="relative rounded-full p-2 isolate"
      onClick={onClick}
      type="button"
    >
      <div className="h-12 w-12" />
      <div className="absolute top-2 transition rotate-180 dark:rotate-0">
        <MoonIcon className="h-12 w-12 text-yellow-400" />
        <SunIcon className="h-12 w-12 text-yellow-400 mt-4" />
      </div>
    </button>
  )
}

To demonstrate how it looks like under the hood, let's disable overflow: hidden:






As we can see, the icons simply switch positions via rotation behind the scenes. Similar to how they are seen in the sky. Pretty neat.

Implementation Details

First off, the button has to be relative to remove the icons from the regular document flow. Without it, we won't be able to achieve the desired rotation transition as seen from the demo above.

Have you noticed the empty div with the classes h-12 and w-12?

export default function ThemeSwitcher({ onClick }) {
  return (
    <button
      {...}
    >
      <div className="h-12 w-12" />
      {...}
    </button>
  );
}

It acts as a placeholder for the absolutely-positioned icons container so that the button dimensions are maintained.

Speaking of button dimensions, each of the icons are required to share the same width and height with that of the placeholder so everything fits in their right places.

export default function ThemeSwitcher({ onClick }) {
  return (
    <button
      {...}
    >
      <div className="h-12 w-12" />
      <div className="...">
        <MoonIcon className="h-12 w-12 ..." />
        <SunIcon className="h-12 w-12 ..." />
      </div>
    </button>
  );
}

Lastly, with the help of Tailwind's dark:* prefix, we can easily toggle the rotation value using dark:rotate-0 when the theme is in dark mode.

Final Thoughts

Tailwind's utility-first fundamentals has helped us design this component without writing any CSS.

The component could still use a lot of improvements like adding a size prop to easily change dimensions, but I decided to keep it simple in this article.

There you have it folks. Thanks for reading!