Cómo agregar Dark Mode con TailwindCSS en Next.js
Stewart Granger Flores - 08 Noviembre, 2020
Voy a asumir que ya tienes tu proyecto Next.js configurado con Tailwind, sino, puedes ver mi tutorial de cómo instalar Tailwind en tu proyecto
Next Themes nos ofrece Hooks para poder manejar el localStorage del usuario sin tener que preparar la configuración nosotros, esto nos permite evitar los típicos problemas de hidratación (cuando hay un flash blanco antes de cargar el tema oscuro en tu página, o el icono de tu Dark Mode no se carga correctamente, etc, etc..)
Ahora instalaremos lo esencial para que esto funcione: Next Themes
npm install next-themes
Ahora en nuestro Tailwind.config.js asegurense de cambiar darkMode a 'class'
const colors = require("tailwindcss/colors");
module.exports = {
purge: [],
presets: [],
darkMode: "class", // or 'media' or 'class'
theme: {
screens: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
"2xl": "1536px",
},
colors: {
transparent: "transparent",
current: "currentColor"
Iremos a _app.js y agregaremos el Provider que nos entrega next-themes
import "../styles/globals.css";
import { ThemeProvider } from "next-themes";
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider attribute="class">
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;
Ahora abrimos el componente donde quieres dejar tu Toggle para cambiar de tema, el Navbar por ejemplo e importamos useTheme que es el Hook que nos permite modificar el localStorage y obtenerlo.
import { useTheme } from "next-themes"
De aquí destructuraremos theme y setTheme
const { theme, setTheme } = useTheme();
Ahora, como cambiaremos el tema en el lado del cliente, usaremos un State para saber si la página ya fue montada o no.
const [isMounted, setIsMounted] = useState(false);
Ahora asegurense de que este es el primer useEffect que se ejecuta en la vista.
useEffect(() => {
setIsMounted(true);
}, []);
Y creamos la función que nos deja cambiar el tema:
const switchTheme = () => {
if (isMounted) {
setTheme(theme === "light" ? "dark" : "light");
}
};
import { useEffect, useState } from "react";
import { useTheme } from "next-themes";
export default function Navbar() {
const [isMounted, setIsMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setIsMounted(true);
}, []);
const switchTheme = () => {
if (isMounted) {
setTheme(theme === "light" ? "dark" : "light");
}
};
return (
<div className="text-center">
<button onClick={switchTheme}>Cambiar tema</button>
</div>
);
}
Con esto ya deberías poder modificar tu tema oscuro, pero antes...
Ahora tenemos control sobre los estados y el dark mode de la página, pero aún falta un paso importante.
Debido a que no podemos qué tema tiene escogido como predeterminado el usuario por el lado del servidor, useTheme será undefined hasta que se monte el lado del cliente. Esto significa que si cargas componentes basados en el tema del usuario (el típico Sol o Ampolleta para los que están en Dark Mode y Lunas para los que están en Light Mode) habrá un periodo donde no concuerden con el tema correcto.
Esto es un factor tomado en cuenta por Google Lighthouse para evitar disconformidad para el usuario. Mejor conocido como Cumulative Layout Shift (CLS) y gira en torno a la estabilidad de la página en su interfaz de usuario.
Para poder arreglar esto, haremos lo siguiente
import { useEffect, useState } from "react";
import { useTheme } from "next-themes";
export default function Navbar() {
const [isMounted, setIsMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setIsMounted(true);
}, []);
const switchTheme = () => {
if (isMounted) {
setTheme(theme === "light" ? "dark" : "light");
}
};
if (!isMounted) return null // <-- Agregar esto
return (
<div className="text-center">
<button onClick={switchTheme}>Cambiar tema</button>
</div>
);
}
Agregaremos if (!isMounted) return null antes de renderizar la página para esperar a que el componente esté montado y sepamos qué tema renderizar al usuario.
Con esto deberías poder agregar clases según quieras a tu página de la siguiente forma:
<h1 className="text-black dark:text-white"> Título de la página </h1>
Acá pueden ver un ejemplo de cómo se ve el Dark Mode funcionando