I have theme change functionality in my app using context. The theme (dark/light) class is being applied, but the colors don't change. I don't know if it's my tailwind config because when I try to type the "bg-primary" in the header, the autocomplete doesn't show the primary var, or it's the ThemeContext logic.
ThemeContext.tsx
import React, { Dispatch, SetStateAction, ReactNode } from "react";
const getInitialTheme = () => {
if (typeof window !== "undefined" && window.localStorage) {
const storedPrefs = window.localStorage.getItem("color-theme") as
| "light"
| "dark"
| null;
if (storedPrefs) {
return storedPrefs;
}
}
return "light";
};
interface ThemeContextType {
theme: "light" | "dark";
setTheme: Dispatch<SetStateAction<"light" | "dark">>;
}
export const ThemeContext = React.createContext<ThemeContextType | undefined>(
undefined,
);
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const [theme, setTheme] = React.useState<"light" | "dark">(getInitialTheme);
const rawSetTheme = (theme: "light" | "dark") => {
const root = window.document.documentElement;
const isDark = theme === "dark";
root.classList.remove(isDark ? "light" : "dark");
root.classList.add(theme);
localStorage.setItem("color-theme", theme);
};
React.useEffect(() => {
rawSetTheme(theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
ThemeToggler.tsx
import { ReactElement } from "react";
import { useTheme } from "../../hooks/useTheme";
import { Sun, Moon } from "lucide-react";
function ThemeToggler(): ReactElement {
const { theme, setTheme } = useTheme();
function toggleTheme() {
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
}
return theme === "light" ? (
<Moon onClick={toggleTheme} />
) : (
<Sun onClick={toggleTheme} />
);
}
export default ThemeToggler;
useTheme.tsx
import { useContext } from "react";
import { ThemeContext } from "../Contexts/ThemeContext";
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme deve ser usado dentro de um ThemeProvider");
}
return context;
};
Header.tsx
import { ReactElement } from "react";
import ThemeToggler from "../ui/ThemeToggler";
function Header(): ReactElement {
return (
<header
className={
"flex h-20 w-svw items-center justify-between px-4 text-xl"
}
>
<div>
<h1>CashierApp</h1>
</div>
<div>
<nav>
<ul className="flex flex-row items-center gap-8">
<li>Home</li>
<li>Controle de caixa</li>
<li>Cadastros</li>
</ul>
</nav>
</div>
<div>
<ThemeToggler />
</div>
</header>
);
}
export default Header;
tailwind.config.ts
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{html,js,jsx,ts,tsx}"],
theme: {
extend: {
colors: {
primary: "var(--color-bg-primary)",
secondary: "var(--color-bg-secondary)",
textPrimary: "var(--color-text-primary)",
textSecondary: "var(--color-text-secondary)",
textAccent: "var(--color-text-accent)",
},
},
},
plugins: [],
};
Edit: I'm using tailwindcss v4, and my css file has the following statements:
@import "tailwindcss";
@tailwind utilities;
Since you're using v4, you don't need the tailwind.config.js
file; instead, you should use the CSS-first directives.
In CSS-first, you can define custom styles using the @theme
directive.
@import "tailwindcss";
@theme {
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 120rem;
--color-avocado-100: oklch(0.99 0 0);
--color-avocado-200: oklch(0.98 0.04 113.22);
--color-avocado-300: oklch(0.94 0.11 115.03);
--color-avocado-400: oklch(0.92 0.19 114.08);
--color-avocado-500: oklch(0.84 0.18 117.33);
--color-avocado-600: oklch(0.53 0.12 118.34);
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
/* ... */
}
@theme
directive - TailwindCSS v4 DocsTheme variables are defined in namespaces and each namespace corresponds to one or more utility class or variant APIs.
However, it is still possible to continue using the tailwind.config.js
through the @config
directive.
Related: