Cómo añadir modo oscuro a una web (CSS, Tailwind, Next.js)
Cómo añadir modo oscuro a una web
Cómo añadir modo oscuro: CSS, Tailwind y Next.js | We.Inc
Añade modo oscuro con prefers-color-scheme, variables CSS y light-dark(), un selector Claro/Oscuro/Sistema, Tailwind v4 y next-themes, sin parpadeo al cargar.
El modo oscuro más sencillo es CSS puro: declara color-scheme: light dark, guarda los colores en propiedades personalizadas y cámbialos dentro de @media (prefers-color-scheme: dark). Para un selector manual, pon una clase o un atributo data en el elemento html, guarda la elección en localStorage y aplícala con un pequeño script en línea en el head para que nunca parpadee el tema equivocado.
Qué enfoque encaja con tu stack
Stack
Sigue al sistema operativo
Selector manual
Pieza clave
CSS puro
Sí
No
@media (prefers-color-scheme: dark)
CSS + script pequeño
Sí
Sí
data-theme en html + script en línea en el head
Tailwind CSS v4
Sí, por defecto
Con @custom-variant
utilidades dark:
Next.js App Router
Sí
Sí
ThemeProvider de next-themes
El modo oscuro tiene tres piezas: colores que pueden cambiar, una forma de decidir qué conjunto usar y el momento adecuado para que la página se pinte bien a la primera. La vista previa de abajo te permite alternar entre claro, oscuro y sistema en un diseño de ejemplo, y después cada sección te da el código exacto para CSS puro, Tailwind y Next.js.
Elige Claro, Oscuro o Sistema en la vista previa. Sistema sigue a tu sistema operativo y se actualiza en directo si lo cambias.
Fíjate en que el campo del formulario y la barra de desplazamiento también cambian. Es la propiedad color-scheme en acción.
Elige tu stack en los ejemplos de abajo y copia el código. El script que evita el parpadeo va en línea dentro del head.
Prueba recargando en cada modo, y una vez con tu sistema operativo en oscuro y nada guardado.
No inviertas sin más el negro puro sobre blanco puro. Unos grises oscuros ligeramente aclarados con texto blanco roto se leen mejor durante mucho rato.
Vuelve a comprobar el contraste en ambos temas. Un azul de marca que pasa sobre blanco puede fallar sobre un fondo gris oscuro.
Dale a las imágenes y los logos una variante oscura, o un fondo transparente, para que una caja blanca no brille en una página oscura.
Guarda la opción sistema como ausencia de valor guardado y no como una palabra, para que un cambio posterior en el sistema operativo se siga respetando.
Añade <meta name="color-scheme" content="light dark"> para que el navegador pinte un lienzo oscuro antes de que llegue tu CSS.
Preguntas frecuentes
¿Cómo evito el destello blanco cuando la página carga en modo oscuro?
Decide el tema antes del primer pintado. Pon un pequeño script bloqueante al principio del head que lea la elección guardada y la preferencia del sistema, y que asigne la clase o el atributo al elemento html. Los scripts al final del body o dentro de un componente del framework se ejecutan demasiado tarde.
¿Cómo funciona el modo oscuro de Tailwind?
En Tailwind v4, la variante dark: usa prefers-color-scheme por defecto, así que dark:bg-gray-900 se aplica cuando el sistema está en oscuro. Para controlarlo con un selector, añade @custom-variant dark (&:where(.dark, .dark *)); a tu CSS y pon la clase dark en el elemento html.
¿Cómo añado modo oscuro en Next.js?
La vía habitual es el paquete next-themes, con licencia MIT. Envuelve la app en su ThemeProvider dentro del layout raíz, añade suppressHydrationWarning a la etiqueta html y usa setTheme de useTheme en un componente de cliente. Inserta por ti el script que evita el parpadeo.
¿Qué hace color-scheme?
Le indica al navegador qué esquemas admite tu página, para que las partes integradas como los controles de formulario, las barras de desplazamiento y el fondo de la página usen colores a juego. Definir color-scheme: light dark en la raíz también es necesario para que la función light-dark() elija su segundo valor.
¿El modo oscuro debe seguir al sistema o tener un selector?
Ambas cosas. Usa por defecto la preferencia del sistema, porque la gente ya la eligió, y ofrece un selector con Claro, Oscuro y Sistema para que el visitante pueda cambiarla solo para tu sitio.
Crea primero la paleta con variables
Nombra los colores por su función, no por su aspecto: --bg, --surface, --text, --muted, --accent, --border. Los componentes solo usan esos nombres. Así, cambiar de tema consiste en redefinir unas pocas variables en un solo lugar.
La función light-dark() lo acorta aún más al guardar ambos valores en una sola declaración. MDN la incluye como Baseline 2024, así que mantén una alternativa para navegadores antiguos si tu público la necesita.
Hacer accesible el selector
Un selector es un botón real con un nombre claro. Para un interruptor de dos estados usa aria-pressed; para tres opciones, un pequeño grupo de botones de radio o un select es más claro que un icono que va rotando.
No anuncies nada más. El cambio visible es la respuesta, y el propio estado del control indica a los usuarios de lectores de pantalla qué está seleccionado.
Renderizado en servidor e hidratación
Un servidor no puede conocer el tema del visitante en la primera petición, así que el HTML renderizado no puede incluir texto específico del tema como Cambiar a claro. Renderiza la etiqueta del selector solo después de que el componente se monte, o renderiza algo neutro.
Por eso también next-themes pide suppressHydrationWarning en html: su script cambia un atributo ahí antes de que React hidrate.
1. CSS puro que sigue al sistema
Variables que cambian con prefers-color-scheme
El marco de resultado sigue la configuración de tu dispositivo. Cambia tu sistema a oscuro y se actualiza sin recargar.
La misma idea con light-dark()
Una declaración por color. Necesita color-scheme en la raíz, y MDN la incluye como Baseline 2024.
2. Un selector Claro, Oscuro y Sistema sin parpadeo
Script en línea al principio del head
Se ejecuta antes de que se pinte el body. Si no hay nada guardado no hace nada, así que la media query sigue al sistema.
CSS que respeta tanto el atributo como el sistema
El atributo manda cuando está presente. Sin él, decide la media query.
El selector en sí
Un select con tres opciones. Elegir Sistema elimina el valor guardado.
3. Tailwind CSS v4
Utilidades dark: con un selector por clase
Sin la línea @custom-variant, dark: sigue a prefers-color-scheme. Con ella, la clase dark en html controla el tema.
4. Next.js App Router con next-themes
Layout raíz y un provider de cliente
ThemeProvider es un componente de cliente, así que va en su propio archivo. attribute="class" lo combina con la clase dark de Tailwind.
Selector que espera a estar montado
El servidor no conoce el tema, así que el control se renderiza tras el montaje para evitar un desajuste de hidratación.
Vista previa del tema
Cambia entre Claro, Oscuro y Sistema para ver cómo cambia un diseño de ejemplo, incluidos sus controles de formulario.
Tema
Claro
Oscuro
Sistema (ahora oscuro)
Sistema (ahora claro)
Panadería del Puerto
Ejemplo
Menú
Visítanos
Recién hecho a las 7am
Masa madre, centeno y panes con semillas cada mañana.