CÓMO CREAR UN INTERRUPTOR DE MODO OSCURO CON TAILWIND CSS
Introducción al Modo Oscuro con Tailwind CSS y Flowbite
El modo oscuro se ha convertido en una funcionalidad esencial para los sitios web modernos, especialmente en plataformas de programación y noticias tecnológicas, donde los usuarios pasan largos períodos frente a la pantalla. Esta característica no solo reduce la fatiga visual, sino que también mejora la experiencia de usuario personalizada, permitiendo a los visitantes elegir entre un tema claro u oscuro según sus preferencias o condiciones de luz. Tailwind CSS, un marco CSS basado en utilidades, ofrece una integración robusta para implementar esta funcionalidad, pero carece de componentes predefinidos para un interruptor de modo oscuro. Aquí es donde Flowbite, una biblioteca de componentes basada en Tailwind CSS, entra en juego, proporcionando elementos interactivos que soportan el modo oscuro de manera nativa desde su versión 1.2.
Este tutorial detallado te guiará paso a paso para configurar un proyecto con Tailwind CSS, instalar Flowbite y construir un interruptor de modo oscuro funcional que los usuarios puedan utilizar para alternar entre temas claro y oscuro. Además, exploraremos cómo integrar este interruptor en una barra de navegación y cómo aprovechar los componentes de Flowbite para mejorar la interfaz de tu sitio web. Todo el código está actualizado a las versiones más recientes de Tailwind CSS (v4) y Flowbite al momento de redacción, noviembre de 2025, asegurando compatibilidad y un rendimiento óptimo.
Configuración Inicial del Proyecto con Tailwind CSS
Antes de implementar el interruptor de modo oscuro, es necesario configurar un proyecto con Tailwind CSS. Este marco CSS basado en utilidades permite crear interfaces modernas y responsivas con un enfoque eficiente. A continuación, se detalla cómo configurar Tailwind CSS desde cero.
Primero, asegúrate de tener Node.js instalado en tu sistema, ya que Tailwind CSS se instala comúnmente como un complemento de PostCSS. Ejecuta el siguiente comando en tu terminal para instalar Tailwind CSS junto con PostCSS y Autoprefixer:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
Este comando instala las dependencias necesarias en modo desarrollo. A continuación, genera un archivo de configuración para PostCSS ejecutando:
npx tailwindcss init -p
Esto crea dos archivos: tailwind.config.js y postcss.config.js. El archivo postcss.config.js debe contener la siguiente configuración para habilitar Tailwind y Autoprefixer:
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Ahora, crea un archivo CSS, por ejemplo, styles.css, en una carpeta como src/css. Agrega las directivas base de Tailwind CSS:
/* src/css/styles.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Compila el archivo CSS con el comando Tailwind CLI para generar el archivo de salida:
npx tailwindcss -i ./src/css/styles.css -o ./dist/css/tailwind.css --watch
Este comando genera un archivo tailwind.css en la carpeta dist/css y observa los cambios en tiempo real. Incluye este archivo en tus plantillas HTML para aplicar los estilos de Tailwind:
<link rel="stylesheet" href="/dist/css/tailwind.css" />
Con esta configuración, tu proyecto está listo para usar Tailwind CSS. Si necesitas más detalles, consulta la documentación oficial de Tailwind CSS.
Instalación de Flowbite como Complemento
Flowbite es una biblioteca de componentes que extiende las capacidades de Tailwind CSS, ofreciendo elementos interactivos como botones, modales y barras de navegación con soporte integrado para el modo oscuro nativo. Para instalar Flowbite, ejecuta el siguiente comando:
npm install flowbite
Luego, incluye Flowbite como un complemento en el archivo tailwind.config.js:
// tailwind.config.js
module.exports = {
plugins: [require("flowbite/plugin")],
};
Además, Flowbite requiere un archivo JavaScript para habilitar la interactividad de sus componentes. Incluye el script de Flowbite antes del cierre de la etiqueta <body> en tu HTML:
<script src="/node_modules/flowbite/dist/flowbite.min.js"></script>
Si estás utilizando un entorno de módulos, puedes importar Flowbite directamente en tu archivo JavaScript principal:
import "flowbite";
Con Flowbite instalado, tienes acceso a componentes preestilizados que soportan el modo oscuro, lo que facilita la creación de interfaces consistentes y responsivas.
Habilitación del Modo Oscuro en Tailwind CSS
Tailwind CSS ofrece dos estrategias principales para habilitar el modo oscuro: la opción media y la opción class. La opción media utiliza la preferencia de esquema de color del sistema operativo del usuario, detectada mediante la consulta CSS prefers-color-scheme. Sin embargo, esta estrategia no permite a los usuarios cambiar manualmente el tema. Por otro lado, la opción class depende de la presencia de una clase .dark en la etiqueta <html>, lo que otorga mayor control al usuario y es ideal para un interruptor de modo oscuro interactivo.
Para este tutorial, utilizaremos la estrategia class. Configura Tailwind CSS para usar esta estrategia editando el archivo tailwind.config.js:
// tailwind.config.js
module.exports = {
darkMode: "class",
plugins: [require("flowbite/plugin")],
};
Para evitar un parpadeo al cargar la página (conocido como FOUC, o Flash of Unstyled Content), agrega un script en la sección <head> de tu HTML que verifique las preferencias previas del usuario en localStorage o use la preferencia del sistema como respaldo:
<head>
<script>
if (
localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
</script>
</head>
Este script asegura que el tema correcto se aplique antes de que el contenido se renderice, mejorando la experiencia del usuario al evitar transiciones abruptas.
Creación del Interruptor de Modo Oscuro
El interruptor de modo oscuro será un botón interactivo que permitirá a los usuarios alternar entre los temas claro y oscuro. Utilizaremos un componente de botón de Flowbite que incluye dos íconos SVG: uno para el modo claro (un sol) y otro para el modo oscuro (una luna). Solo uno de estos íconos se mostrará a la vez, dependiendo del tema activo.
Agrega el siguiente código HTML en tu página, preferiblemente en la esquina superior derecha de la barra de navegación, ya que es una ubicación intuitiva para los usuarios:
<button
id="theme-toggle"
type="button"
class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5"
>
<svg
id="theme-toggle-dark-icon"
class="w-5 h-5 hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"
></path>
</svg>
<svg
id="theme-toggle-light-icon"
class="w-5 h-5 hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"
></path>
</svg>
</button>
Los identificadores utilizados son:
#theme-toggle: Para el botón principal del interruptor.#theme-toggle-dark-icon: Para el ícono de la luna, visible en el modo claro.#theme-toggle-light-icon: Para el ícono del sol, visible en el modo oscuro.
Manejo del Interruptor con JavaScript
Para que el interruptor sea funcional, necesitamos manejar los eventos de clic y actualizar tanto el estado del tema en localStorage como los íconos mostrados. Agrega el siguiente código en tu archivo JavaScript principal:
// main.js
const themeToggleDarkIcon = document.getElementById("theme-toggle-dark-icon");
const themeToggleLightIcon = document.getElementById("theme-toggle-light-icon");
// Cambiar los íconos según las preferencias previas
if (
localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
themeToggleLightIcon.classList.remove("hidden");
} else {
themeToggleDarkIcon.classList.remove("hidden");
}
const themeToggleBtn = document.getElementById("theme-toggle");
themeToggleBtn.addEventListener("click", () => {
// Alternar visibilidad de los íconos
themeToggleDarkIcon.classList.toggle("hidden");
themeToggleLightIcon.classList.toggle("hidden");
// Si hay una preferencia previa en localStorage
if (localStorage.getItem("color-theme")) {
if (localStorage.getItem("color-theme") === "light") {
document.documentElement.classList.add("dark");
localStorage.setItem("color-theme", "dark");
} else {
document.documentElement.classList.remove("dark");
localStorage.setItem("color-theme", "light");
}
} else {
// Si no hay preferencia previa
if (document.documentElement.classList.contains("dark")) {
document.documentElement.classList.remove("dark");
localStorage.setItem("color-theme", "light");
} else {
document.documentElement.classList.add("dark");
localStorage.setItem("color-theme", "dark");
}
}
});
Este código realiza dos funciones principales:
- Inicializa el ícono correcto según la preferencia almacenada o la configuración del sistema.
- Maneja los clics en el botón para alternar entre temas, actualizando tanto la clase
.darken el elemento<html>como el valor enlocalStorage.
Integración del Interruptor en la Barra de Navegación
La barra de navegación es un lugar ideal para colocar el interruptor de modo oscuro, ya que es fácilmente accesible y visible. Flowbite ofrece componentes de barra de navegación preestilizados que se integran perfectamente con Tailwind CSS y soportan el modo oscuro. A continuación, se muestra un ejemplo de cómo integrar el interruptor en una barra de navegación:
<nav
class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 rounded dark:bg-gray-800"
>
<div class="container mx-auto flex flex-wrap items-center justify-between">
<a href="#" class="flex items-center">
<svg
class="h-10 mr-3"
viewBox="0 0 52 72"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.87695 53H28.7791C41.5357 53 51.877 42.7025 51.877 30H24.9748C12.2182 30 1.87695 40.2975 1.87695 53Z"
fill="#76A9FA"
></path>
<path
d="M0.000409561 32.1646L0.000409561 66.4111C12.8618 66.4111 23.2881 55.9849 23.2881 43.1235L23.2881 8.87689C10.9966 8.98066 1.39567 19.5573 0.000409561 32.1646Z"
fill="#A4CAFE"
></path>
<path
d="M50.877 5H23.9748C11.2182 5 0.876953 15.2975 0.876953 28H27.7791C40.5357 28 50.877 17.7025 50.877 5Z"
fill="#1C64F2"
></path>
</svg>
<span
class="self-center text-lg font-semibold whitespace-nowrap dark:text-white"
>Flowbite</span
>
</a>
<div class="flex md:order-2">
<button
id="theme-toggle"
type="button"
class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5"
>
<svg
id="theme-toggle-dark-icon"
class="w-5 h-5 hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"
></path>
</svg>
<svg
id="theme-toggle-light-icon"
class="w-5 h-5 hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"
></path>
</svg>
</button>
<button
data-collapse-toggle="mobile-menu-4"
type="button"
class="md:hidden text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 rounded-lg text-sm p-2 inline-flex items-center dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="mobile-menu-4"
aria-expanded="false"
>
<span class="sr-only">Abrir menú principal</span>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd"
></path>
</svg>
<svg
class="hidden w-6 h-6"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</button>
</div>
<div
class="hidden md:flex justify-between items-center w-full md:w-auto md:order-1"
id="mobile-menu-4"
>
<ul
class="flex-col md:flex-row flex md:space-x-8 mt-4 md:mt-0 md:text-sm md:font-medium"
>
<li>
<a
href="#"
class="bg-blue-700 md:bg-transparent text-white block pl-3 pr-4 py-2 md:text-blue-700 md:p-0 rounded dark:text-white"
aria-current="page"
>
Inicio
</a>
</li>
<li>
<a
href="#"
class="text-gray-700 hover:bg-gray-50 border-b border-gray-100 md:hover:bg-transparent md:border-0 block pl-3 pr-4 py-2 md:hover:text-blue-700 md:p-0 md:dark:hover:text-white dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
>
Acerca
</a>
</li>
<li>
<a
href="#"
class="text-gray-700 hover:bg-gray-50 border-b border-gray-100 md:hover:bg-transparent md:border-0 block pl-3 pr-4 py-2 md:hover:text-blue-700 md:p-0 md:dark:hover:text-white dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
>
Servicios
</a>
</li>
<li>
<a
href="#"
class="text-gray-700 hover:bg-gray-50 border-b border-gray-100 md:hover:bg-transparent md:border-0 block pl-3 pr-4 py-2 md:hover:text-blue-700 md:p-0 md:dark:hover:text-white dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
>
Contacto
</a>
</li>
</ul>
</div>
</div>
</nav>
Este código crea una barra de navegación responsiva con el logotipo de Flowbite, enlaces de navegación y el interruptor de modo oscuro. Los estilos dark: de Tailwind aseguran que los colores se ajusten automáticamente al cambiar entre temas.
Uso de Componentes de Flowbite en Modo Oscuro
Flowbite ofrece una amplia gama de componentes que soportan el modo oscuro de forma nativa, lo que facilita la creación de interfaces consistentes. Desde la versión 1.2, todos los componentes de Flowbite, como modales, botones y menús desplegables, incluyen clases dark: que ajustan automáticamente su apariencia según el tema activo.
Por ejemplo, un componente de modal puede cambiar su fondo y colores de texto según el tema. Aquí hay un ejemplo de un modal de Flowbite:
<div
id="modalEl"
tabindex="-1"
aria-hidden="true"
class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full"
>
<div class="relative w-full max-w-2xl max-h-full">
<div class="relative bg-white rounded-lg shadow-sm dark:bg-gray-700">
<div
class="flex items-start justify-between p-5 border-b rounded-t dark:border-gray-600"
>
<h3
class="text-xl font-semibold text-gray-900 lg:text-2xl dark:text-white"
>
Términos de Servicio
</h3>
<button
type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
data-modal-hide="modalEl"
>
<svg
class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
/>
</svg>
<span class="sr-only">Cerrar modal</span>
</button>
</div>
<div class="p-6 space-y-6">
<p
class="text-base leading-relaxed text-gray-500 dark:text-gray-400"
>
Con menos de un mes para que la Unión Europea implemente
nuevas leyes de privacidad, las empresas de todo el mundo
están actualizando sus acuerdos de términos de servicio para
cumplirlas.
</p>
</div>
<div
class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600"
>
<button
data-modal-hide="modalEl"
type="button"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Aceptar
</button>
<button
data-modal-hide="modalEl"
type="button"
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
>
Rechazar
</button>
</div>
</div>
</div>
</div>
Para controlar este modal con JavaScript, puedes usar la API de Flowbite:
// main.js
import { Modal } from "flowbite";
const $targetEl = document.getElementById("modalEl");
const modal = new Modal($targetEl);
document.getElementById("openModalBtn").addEventListener("click", () => {
modal.show();
});
document.getElementById("closeModalBtn").addEventListener("click", () => {
modal.hide();
});
Este modal cambia automáticamente su apariencia entre los modos claro y oscuro gracias a las clases dark: aplicadas a sus elementos, como dark:bg-gray-700 para el fondo y dark:text-white para el texto.
Personalización Avanzada del Modo Oscuro
Para proyectos más complejos, puedes personalizar los colores y estilos del modo oscuro definiendo variables CSS en tu archivo styles.css. Por ejemplo, agrega un tema personalizado con una paleta de colores:
/* src/css/styles.css */
@import "tailwindcss";
@plugin "flowbite/plugin";
@source "../node_modules/flowbite";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-primary-500: #3b82f6;
--color-primary-600: #2563eb;
--color-background: #ffffff;
--color-text: #1f2937;
}
.dark {
--color-background: #1f2937;
--color-text: #f9fafb;
}
Estas variables permiten aplicar colores personalizados a los elementos en ambos temas. Por ejemplo:
<div class="bg-[--color-background] text-[--color-text]">
Contenido con colores personalizados
</div>
Esta configuración asegura que el fondo y el texto se ajusten automáticamente según el tema activo, proporcionando una experiencia visual coherente.
Optimización para Producción
Para optimizar tu proyecto para producción, considera las siguientes prácticas:
- Minificación de CSS: Usa herramientas como
cssnanopara reducir el tamaño del archivo CSS generado por Tailwind. - Purgado de clases no utilizadas: Configura el purgado en
tailwind.config.jspara eliminar las clases no utilizadas y reducir el tamaño del CSS:
// tailwind.config.js
module.exports = {
darkMode: "class",
content: [
"./src/**/*.{html,js,ts,jsx,tsx}",
"./node_modules/flowbite/**/*.js",
],
plugins: [require("flowbite/plugin")],
};
- Carga condicional de JavaScript: Si usas un entorno de módulos, importa solo los componentes de Flowbite que necesitas para minimizar el impacto en el rendimiento.
Conclusiones
Implementar un interruptor de modo oscuro con Tailwind CSS y Flowbite es un proceso accesible que mejora significativamente la experiencia del usuario en sitios web de programación y tecnología. Al configurar Tailwind CSS con la estrategia class, instalar Flowbite y agregar un botón interactivo, puedes ofrecer a los usuarios la posibilidad de alternar entre temas claro y oscuro de manera fluida. Los componentes de Flowbite, como barras de navegación y modales, facilitan la creación de interfaces modernas que se adaptan automáticamente al tema seleccionado. Con las personalizaciones avanzadas y las optimizaciones para producción, tu sitio estará preparado para ofrecer una experiencia visualmente atractiva y eficiente. Este enfoque no solo mejora la usabilidad, sino que también posiciona tu sitio como una solución moderna en el panorama del desarrollo web.