Create Custom Themes in Tailwind CSS v4 – @custom-variant (1/2)



This content originally appeared on DEV Community and was authored by vrauuss softwares

Themes define the visual style and layout of a website, making the user experience more enjoyable. In this article, I’ll show you how to create different themes using Tailwind CSS.

Note: I used AI tools to help draft and structure this article because I’m still building my technical writing skills. However, all the code was written, tested, and reviewed by me.

Featured Image

1. Add custom variants with @custom-variant

You can create new themes or conditional styles through custom variants added by the @custom-variant directive. But first, here are some important points to consider:

  • Define in index.css: Add @custom-variant declarations to your main CSS file (e.g., index.css).

  • Enable dark mode: Create a dark variant and activate it using the data-theme attribute.

  • Maintain consistent naming: Use a short name like dark for dark mode, and prefix other themes with theme-.

Method 1: Nesting syntax

This method allows nesting additional rules within the block:

@custom-variant dark {
    &:where([data-theme="dark"] *) {
        @slot;
    }
}

@custom-variant theme-midnight {
    &:where([data-theme="midnight"] *) {
        @slot;
    }
}

Example applying styles only on devices that support hover:

@custom-variant any-hover {
    @media (any-hover: hover) {
        &:hover {
            @slot;
        }
    }
}

Note: This rule only applies to devices that support hover (e.g., desktops or laptops), and not to touch-only devices like phones or tablets.

Method 2: Shortcut syntax

This syntax is useful when you don’t need to nest other rules:

@custom-variant dark (&:where([data-theme="dark"] *));
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));

2. Apply theme-specific styles in markup

Once data-theme is set, you can apply conditional styles using your custom variant prefix in your utility classes:

<body class="bg-white theme-dark:bg-black-800 theme-midnight:bg-purple-800">

3. Set themes via data-theme attribute

To activate a theme, set the data-theme attribute on the <html> tag:

<html data-theme="midnight" lang="en">

You can dynamically change the value to switch themes:

<html data-theme="light" lang="en"> <!-- No need to add custom variant -->
<html data-theme="dark" lang="en">
<html data-theme="midnight" lang="en">

4. Bonus: Component to switch between themes dynamically

This example uses a <select> element and updates the data-theme attribute on <html> to control the active theme.

import React, { useState, useEffect } from "react";
import capitalize from "./utils/capitalize"; // Helper to capitalize names

// Available themes
const themes = [
  { name: "light", icon: "" },
  { name: "dark", icon: "🌑" },
  { name: "midnight", icon: "🌌" },
];

function App() {
  // State for the current theme index
  const [idx, setIdx] = useState(0);
  const theme = themes[idx];

  // Update data-theme when theme changes
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", theme.name);
  }, [theme]);

  // Update index state based on the selected theme
  const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setIdx(themes.findIndex((t) => t.name === e.target.value));
  };

  return (
    <div className="space-y-2 text-center">
      <label htmlFor="theme-select" className="block font-medium">
        Choose a theme:
      </label>

      <select
        id="theme-select"
        value={theme.name}
        onChange={handleChange}
        className="rounded border px-3 py-1"
      >
        {themes.map((theme) => (
          <option key={theme.name} value={theme.name}>
            {theme.icon} {capitalize(theme.name)}
          </option>
        ))}
      </select>
    </div>
  );
}

export default App;

Note: Soon, I’ll create a customizable and more complete <SwitchTheme> component.

Contact & Links

Contact

Blog

  • Coming soon…

Articles

  • YouTube: Coming soon…
  • DEV.to: Coming soon…

References


This content originally appeared on DEV Community and was authored by vrauuss softwares