Saltar al contenido principal

[Medium] 🎨 Implementación de cambio de temas múltiples

Pregunta de escenario para entrevista

Q: Cuando una página necesita 2 estilos diferentes (por ejemplo, tema claro/oscuro), cómo organizas el CSS?

Esta es una pregunta que evalúa el diseño de arquitectura CSS y la experiencia práctica, que involucra:

  1. Diseño de arquitectura CSS
  2. Estrategia de cambio de temas
  3. Aplicación de herramientas modernas (Tailwind CSS, CSS Variables)
  4. Consideraciones de rendimiento y mantenibilidad

Visión general de soluciones

SoluciónEscenario de usoVentajasDesventajasRecomendación
CSS VariablesProyectos con navegadores modernosCambio dinámico, buen rendimientoNo soporta IE5/5 Muy recomendado
Quasar + Pinia + SCSSProyectos Vue 3 + QuasarEcosistema completo, gestión de estadoRequiere Quasar Framework5/5 Muy recomendado
Tailwind CSSDesarrollo rápido, design systemDesarrollo rápido, alta consistenciaCurva de aprendizaje, HTML extenso5/5 Muy recomendado
CSS Class toggleCompatibilidad con navegadores antiguosBuena compatibilidadCSS más pesado4/5 Recomendado
CSS ModulesProyectos React/Vue componentizadosAislamiento de scopeRequiere herramientas de build4/5 Recomendado
Styled ComponentsProyectos ReactCSS-in-JS, estilos dinámicosOverhead en runtime4/5 Recomendado
Variables SASS/LESSTema decidido en compilaciónFunciones potentesNo permite cambio dinámico3/5 Considerar
Archivos CSS independientesTemas muy diferentesSeparación claraOverhead de carga, código duplicado2/5 No recomendado

Solución 1: CSS Variables

Concepto central

Usar propiedades personalizadas de CSS (CSS Custom Properties), cambiando los valores de las variables al alternar la class del elemento raíz.

Implementación

1. Definir variables de tema

/* styles/themes.css */

/* Tema claro (predeterminado) */
:root {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-background: #ffffff;
--color-text: #1f2937;
--color-border: #e5e7eb;
--color-card: #f9fafb;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* Tema oscuro */
[data-theme='dark'] {
--color-primary: #60a5fa;
--color-secondary: #a78bfa;
--color-background: #1f2937;
--color-text: #f9fafb;
--color-border: #374151;
--color-card: #111827;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

/* Si hay un tercer tema (por ejemplo, modo protección visual) */
[data-theme='sepia'] {
--color-primary: #92400e;
--color-secondary: #78350f;
--color-background: #fef3c7;
--color-text: #451a03;
--color-border: #fde68a;
--color-card: #fef9e7;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

2. Usar variables

/* components/Button.css */
.button {
background-color: var(--color-primary);
color: var(--color-text);
border: 1px solid var(--color-border);
box-shadow: var(--shadow);
transition: all 0.3s ease;
}

.card {
background-color: var(--color-card);
color: var(--color-text);
border: 1px solid var(--color-border);
}

body {
background-color: var(--color-background);
color: var(--color-text);
}

3. Cambio de tema con JavaScript

// utils/theme.js

// Obtener tema actual
function getCurrentTheme() {
return localStorage.getItem('theme') || 'light';
}

// Establecer tema
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}

// Alternar tema
function toggleTheme() {
const currentTheme = getCurrentTheme();
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
}

// Inicializar (leer preferencia del usuario desde localStorage)
function initTheme() {
const savedTheme = getCurrentTheme();
setTheme(savedTheme);

// Escuchar cambios del tema del sistema
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
// Si el usuario no ha establecido preferencia, seguir el sistema
setTheme(e.matches ? 'dark' : 'light');
}
});
}

// Inicializar al cargar la página
initTheme();

4. Ejemplo de integración con Vue 3

<template>
<div>
<button @click="toggleTheme" class="theme-toggle">
<span v-if="currentTheme === 'light'">🌙 Modo oscuro</span>
<span v-else>☀️ Modo claro</span>
</button>
</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const currentTheme = ref('light');

function toggleTheme() {
const newTheme = currentTheme.value === 'light' ? 'dark' : 'light';
currentTheme.value = newTheme;
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}

onMounted(() => {
const savedTheme = localStorage.getItem('theme') || 'light';
currentTheme.value = savedTheme;
document.documentElement.setAttribute('data-theme', savedTheme);
});
</script>

Ventajas

  • Cambio dinámico: No necesita recargar archivos CSS
  • Buen rendimiento: Soporte nativo del navegador, solo cambia valores de variables
  • Fácil mantenimiento: Gestión centralizada de temas, modificación conveniente
  • Extensible: Fácil agregar un tercer o cuarto tema

Desventajas

  • IE no soportado: Necesita polyfill o solución alternativa
  • Integración con preprocesadores: Cuidado al mezclar con variables SASS/LESS

Solución 2: Tailwind CSS

Concepto central

Usar la variante dark: de Tailwind CSS y la configuración de temas personalizados, combinado con el cambio de class para implementar el tema.

Implementación

1. Configurar Tailwind

// tailwind.config.js
module.exports = {
darkMode: 'class', // Usar estrategia de class (no media query)
theme: {
extend: {
colors: {
// Colores personalizados (se pueden definir múltiples conjuntos de colores de tema)
primary: {
light: '#3b82f6',
dark: '#60a5fa',
},
background: {
light: '#ffffff',
dark: '#1f2937',
},
text: {
light: '#1f2937',
dark: '#f9fafb',
},
},
},
},
plugins: [],
};

2. Usar clases de tema de Tailwind

<template>
<!-- Método 1: Usar variante dark: -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
<h1 class="text-blue-600 dark:text-blue-400">Título</h1>

<button
class="bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white px-4 py-2 rounded"
>
Botón
</button>

<div
class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md dark:shadow-lg"
>
<p class="text-gray-700 dark:text-gray-300">Texto de contenido</p>
</div>
</div>

<!-- Botón de cambio de tema -->
<button @click="toggleTheme" class="fixed top-4 right-4">
<svg v-if="isDark" class="w-6 h-6">
<!-- Icono de sol -->
</svg>
<svg v-else class="w-6 h-6">
<!-- Icono de luna -->
</svg>
</button>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const isDark = ref(false);

function toggleTheme() {
isDark.value = !isDark.value;
updateTheme();
}

function updateTheme() {
if (isDark.value) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
}
}

onMounted(() => {
// Leer preferencia de tema guardada
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

isDark.value = savedTheme === 'dark' || (!savedTheme && prefersDark);
updateTheme();
});
</script>

3. Avanzado: Personalizar múltiples temas (más de 2)

// tailwind.config.js
module.exports = {
darkMode: 'class',
theme: {
extend: {
colors: {
theme: {
bg: 'var(--theme-bg)',
text: 'var(--theme-text)',
primary: 'var(--theme-primary)',
},
},
},
},
};
/* styles/themes.css */
:root {
--theme-bg: #ffffff;
--theme-text: #000000;
--theme-primary: #3b82f6;
}

[data-theme='dark'] {
--theme-bg: #1f2937;
--theme-text: #f9fafb;
--theme-primary: #60a5fa;
}

[data-theme='sepia'] {
--theme-bg: #fef3c7;
--theme-text: #451a03;
--theme-primary: #92400e;
}
<template>
<!-- Usar variables de tema personalizadas -->
<div class="bg-theme-bg text-theme-text">
<button class="bg-theme-primary">Botón</button>
</div>

<!-- Selector de tema -->
<select @change="setTheme($event.target.value)">
<option value="light">Claro</option>
<option value="dark">Oscuro</option>
<option value="sepia">Protección visual</option>
</select>
</template>

<script setup>
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}
</script>

Ventajas de Tailwind

  • Desarrollo rápido: utility-first, no necesita escribir CSS
  • Consistencia: Design system integrado, mantiene estilo uniforme
  • tree-shaking: Elimina automáticamente estilos no usados
  • Amigable con RWD: Variantes responsivas sm:, md:, lg:
  • Variantes de tema: dark:, hover:, focus: y más variantes ricas

Desventajas

  • HTML extenso: Muchas clases, puede afectar legibilidad
  • Curva de aprendizaje: Necesita familiarizarse con nombres de utility class
  • Personalización: Personalización profunda requiere conocer la configuración

Solución 3: Quasar + Pinia + SCSS (experiencia reciente)

Experiencia real en proyecto: Esta es la solución que utilicé en un proyecto real, integrando Quasar Framework, gestión de estado con Pinia y sistema de variables SCSS.

Concepto central

Adoptar diseño de arquitectura multicapa:

  1. Quasar Dark Mode API - Soporte de temas a nivel de framework
  2. Pinia Store - Gestión centralizada del estado del tema
  3. SessionStorage - Persistencia de preferencias del usuario
  4. SCSS Variables + Mixin - Variables de tema y gestión de estilos

Flujo de arquitectura

Usuario hace clic en botón de cambio

Quasar $q.dark.toggle()

Pinia Store actualiza estado

Sincronizar con SessionStorage

Cambio de class en Body (.body--light / .body--dark)

Actualización de CSS Variables

UI se actualiza automáticamente

Implementación

1. Pinia Store (gestión de estado)

// src/stores/darkModeStore.ts
import { defineStore } from 'pinia';
import { useSessionStorage } from '@vueuse/core';

export const useDarkModeStore = defineStore('darkMode', () => {
// Persistir estado con SessionStorage
const isDarkMode = useSessionStorage<boolean>('isDarkMode', false);

// Actualizar estado de Dark Mode
const updateIsDarkMode = (status: boolean) => {
isDarkMode.value = status;
};

return {
isDarkMode,
updateIsDarkMode,
};
});

2. Configuración de Quasar

// quasar.config.js
module.exports = configure(function (/* ctx */) {
return {
framework: {
config: {
dark: 'true', // Habilitar soporte de Dark Mode
},
plugins: ['Notify', 'Loading', 'Dialog'],
},
};
});

3. Sistema de variables de tema SCSS

// assets/css/_variable.scss

// Definir mapeo de variables para temas Light y Dark
$themes: (
light: (
--bg-main: #ffffff,
--bg-side: #f0f1f4,
--text-primary: #000000,
--text-secondary: #666666,
--primary-color: #2d7eff,
--border-color: #e5ebf2,
),
dark: (
--bg-main: #081f2d,
--bg-side: #0d2533,
--text-primary: #ffffff,
--text-secondary: #b0b0b0,
--primary-color: #2d7eff,
--border-color: #14384d,
),
);

// Mixin: Aplicar CSS Variables según el tema
@mixin theme-vars($theme) {
@each $key, $value in map-get($themes, $theme) {
#{$key}: #{$value};
}
}

// Mixin: Estilos exclusivos para Light Mode
@mixin light {
.body--light & {
@content;
}
}

// Mixin: Estilos exclusivos para Dark Mode
@mixin dark {
.body--dark & {
@content;
}
}

4. Aplicación global del tema

// src/css/app.scss
@import 'assets/css/_variable.scss';

// Aplicar Light Theme por defecto
:root {
@include theme-vars('light');
}

// Aplicar Dark Theme en Dark Mode
.body--dark {
@include theme-vars('dark');
}

5. Uso en componentes

Método A: Usar CSS Variables (recomendado)

<template>
<div class="my-card">
<h2 class="title">Título</h2>
<p class="content">Texto de contenido</p>
</div>
</template>

<style scoped lang="scss">
.my-card {
background: var(--bg-main);
color: var(--text-primary);
border: 1px solid var(--border-color);
padding: 1rem;
}

.title {
color: var(--primary-color);
font-size: 1.5rem;
}

.content {
color: var(--text-secondary);
}
</style>

Método B: Usar SCSS Mixin (avanzado)

<template>
<button class="custom-btn">Botón</button>
</template>

<style scoped lang="scss">
@import 'assets/css/_variable.scss';

.custom-btn {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: all 0.3s ease;

@include light {
background: #2d7eff;
color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);

&:hover {
background: #1a5fd9;
}
}

@include dark {
background: #1677ff;
color: #ffffff;
box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1);

&:hover {
background: #0d5acc;
}
}
}
</style>

6. Función de cambio

<template>
<button @click="toggleDarkMode" class="theme-toggle">
<q-icon :name="isDarkMode ? 'light_mode' : 'dark_mode'" />
{{ isDarkMode ? 'Cambiar a claro' : 'Cambiar a oscuro' }}
</button>
</template>

<script setup lang="ts">
import { useQuasar } from 'quasar';
import { onMounted } from 'vue';
import { useDarkModeStore } from 'stores/darkModeStore';

const $q = useQuasar();
const { isDarkMode, updateIsDarkMode } = useDarkModeStore();

// Cambiar tema
const toggleDarkMode = () => {
$q.dark.toggle(); // Cambio Quasar
updateIsDarkMode($q.dark.isActive); // Sincronizar con Store
};

// Restaurar preferencia del usuario al cargar página
onMounted(() => {
if (isDarkMode.value) {
$q.dark.set(true);
}
});
</script>

<style scoped lang="scss">
.theme-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--primary-color);
color: var(--text-primary);
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: opacity 0.3s ease;

&:hover {
opacity: 0.8;
}
}
</style>

Ventajas

  • Ecosistema completo: Quasar + Pinia + VueUse solución integral
  • Gestión de estado: Pinia gestión centralizada, fácil de probar y mantener
  • Persistencia: SessionStorage guardado automático, no se pierde al refrescar
  • Tipo seguro: Soporte TypeScript, reduce errores
  • Experiencia de desarrollo: SCSS Mixin simplifica desarrollo de estilos
  • Buen rendimiento: CSS Variables actualización dinámica, sin necesidad de recargar

Desventajas

  • Dependencia del framework: Requiere usar Quasar Framework
  • Costo de aprendizaje: Necesita familiarizarse con Quasar, Pinia, SCSS
  • Mayor tamaño: Framework completo es más pesado que CSS puro

Mejores prácticas

// composables/useTheme.ts
import { computed } from 'vue';
import { useQuasar } from 'quasar';
import { useDarkModeStore } from 'stores/darkModeStore';

export function useTheme() {
const $q = useQuasar();
const store = useDarkModeStore();

const isDark = computed(() => store.isDarkMode);

const toggleTheme = () => {
$q.dark.toggle();
store.updateIsDarkMode($q.dark.isActive);
};

const setTheme = (dark: boolean) => {
$q.dark.set(dark);
store.updateIsDarkMode(dark);
};

return {
isDark,
toggleTheme,
setTheme,
};
}

Cómo presentarlo en una entrevista

"En mi último proyecto, implementamos un sistema completo de Dark Mode con Quasar + Pinia + SCSS:

  1. Gestión de estado: Gestión unificada del estado del tema con Pinia Store, persistencia con useSessionStorage de VueUse
  2. Sistema de estilos: Variables de tema definidas con Map + Mixin de SCSS, aplicadas en :root y .body--dark
  3. Mecanismo de cambio: Control a través de la API $q.dark de Quasar, agrega automáticamente la class correspondiente al <body>
  4. Experiencia de desarrollo: Mixin @include light y @include dark para un desarrollo de estilos de componentes más intuitivo

Esta solución funcionó bien en nuestro proyecto, con cambios fluidos, estado estable y fácil mantenimiento."


Solución 4: Cambio de CSS Class

Implementación

/* styles/themes.css */

/* Tema claro */
body.theme-light {
background-color: #ffffff;
color: #000000;
}

body.theme-light .button {
background-color: #3b82f6;
color: #ffffff;
}

body.theme-light .card {
background-color: #f9fafb;
border: 1px solid #e5e7eb;
}

/* Tema oscuro */
body.theme-dark {
background-color: #1f2937;
color: #f9fafb;
}

body.theme-dark .button {
background-color: #60a5fa;
color: #000000;
}

body.theme-dark .card {
background-color: #111827;
border: 1px solid #374151;
}
// Cambiar tema
function setTheme(theme) {
document.body.className = `theme-${theme}`;
localStorage.setItem('theme', theme);
}

Escenarios de uso

  • Necesidad de soportar navegadores antiguos como IE
  • Las diferencias de tema son grandes, no es adecuado usar variables
  • No se quiere introducir dependencias adicionales

Solución 5: Archivos CSS independientes (no recomendado)

Implementación

<!-- Carga dinámica de CSS -->
<link id="theme-stylesheet" rel="stylesheet" href="/styles/theme-light.css" />
function setTheme(theme) {
const link = document.getElementById('theme-stylesheet');
link.href = `/styles/theme-${theme}.css`;
}

Desventajas

  • Overhead de carga: Necesita re-descargar CSS al cambiar
  • FOUC: Puede aparecer un breve parpadeo sin estilos
  • Código duplicado: Estilos compartidos necesitan definirse repetidamente

Integración con diseño responsivo RWD

Tailwind CSS + RWD + Cambio de tema

<template>
<div
class="
/* Estilos base */
p-4 rounded-lg transition-colors

/* Tema claro */
bg-white text-gray-900

/* Tema oscuro */
dark:bg-gray-800 dark:text-gray-100

/* RWD: Móvil */
text-sm

/* RWD: Tableta y superior */
md:text-base md:p-6

/* RWD: Escritorio y superior */
lg:text-lg lg:p-8

/* Estado de interacción */
hover:shadow-lg hover:scale-105
"
>
<h2
class="
font-bold
text-xl md:text-2xl lg:text-3xl
text-blue-600 dark:text-blue-400
"
>
Título responsivo
</h2>

<p class="mt-2 text-gray-700 dark:text-gray-300">Texto de contenido</p>

<!-- Grid responsivo -->
<div
class="
grid
grid-cols-1 /* Móvil: 1 columna */
sm:grid-cols-2 /* Tableta pequeña: 2 columnas */
md:grid-cols-3 /* Tableta: 3 columnas */
lg:grid-cols-4 /* Escritorio: 4 columnas */
gap-4
"
>
<div
v-for="item in items"
:key="item.id"
class="
p-4 rounded
bg-gray-100 dark:bg-gray-700
hover:bg-gray-200 dark:hover:bg-gray-600
"
>
{{ item.name }}
</div>
</div>
</div>
</template>

CSS Variables + Media Queries

/* Variables base */
:root {
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--font-size-base: 16px;
}

/* Ajuste de espaciado para tableta y superior */
@media (min-width: 768px) {
:root {
--spacing-sm: 0.75rem;
--spacing-md: 1.5rem;
--spacing-lg: 2rem;
}
}

/* Ajuste de fuente para escritorio y superior */
@media (min-width: 1024px) {
:root {
--font-size-base: 18px;
}
}

/* Usar variables */
.container {
padding: var(--spacing-md);
font-size: var(--font-size-base);
}

/* Tema oscuro + RWD */
@media (min-width: 768px) {
[data-theme='dark'] {
--shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
}
}

Sugerencias de optimización de rendimiento

1. Evitar FOUC (Flash of Unstyled Content)

<!-- Ejecutar inmediatamente en <head>, evitar parpadeo -->
<script>
(function () {
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);
if (theme === 'dark') {
document.documentElement.classList.add('dark');
}
})();
</script>

2. Usar prefers-color-scheme

/* Detección automática del tema del sistema */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
/* Si el usuario no ha configurado preferencia, seguir el sistema */
--color-background: #1f2937;
--color-text: #f9fafb;
}
}
// Detección con JavaScript
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (!localStorage.getItem('theme')) {
setTheme(prefersDark ? 'dark' : 'light');
}

3. Transiciones de animación CSS

/* Transición suave */
* {
transition: background-color 0.3s ease, color 0.3s ease,
border-color 0.3s ease;
}

/* O para elementos específicos */
body,
.card,
.button {
transition: all 0.3s ease;
}

4. Reducir Reflow

/* Usar transform en vez de cambiar ancho/alto directamente */
.theme-switching {
transform: scale(1);
transition: transform 0.3s ease;
}

.theme-switching:hover {
transform: scale(1.05); /* Aceleración GPU */
}

Arquitectura de proyecto real

Estructura de archivos

src/
├── styles/
│ ├── themes/
│ │ ├── variables.css # Definición de CSS Variables
│ │ ├── light.css # Tema claro
│ │ ├── dark.css # Tema oscuro
│ │ └── sepia.css # Tema protección visual
│ ├── base.css # Estilos base
│ └── components/ # Estilos de componentes
│ ├── button.css
│ └── card.css
├── utils/
│ └── theme.js # Lógica de cambio de tema
└── components/
└── ThemeToggle.vue # Componente de cambio de tema

Mejores prácticas

// composables/useTheme.js (Vue 3 Composition API)
import { ref, onMounted, watch } from 'vue';

export function useTheme() {
const theme = ref('light');
const themes = ['light', 'dark', 'sepia'];

function setTheme(newTheme) {
if (!themes.includes(newTheme)) return;

theme.value = newTheme;
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);

// Tailwind dark mode
if (newTheme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}

function toggleTheme() {
const currentIndex = themes.indexOf(theme.value);
const nextIndex = (currentIndex + 1) % themes.length;
setTheme(themes[nextIndex]);
}

function initTheme() {
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches;

if (savedTheme) {
setTheme(savedTheme);
} else if (prefersDark) {
setTheme('dark');
}
}

onMounted(() => {
initTheme();

// Escuchar cambios del tema del sistema
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
});

return {
theme,
themes,
setTheme,
toggleTheme,
};
}

Plantilla de respuesta para entrevista

Entrevistador: Cuando una página necesita 2 estilos diferentes, cómo organizas el CSS?

Método de respuesta A: Mostrar experiencia real (recomendado)

"Elijo la solución más adecuada según el stack tecnológico del proyecto. En mi último proyecto, usamos Quasar + Pinia + SCSS:

1. Gestión de estado (30 segundos)

  • Gestión unificada del estado del tema con Pinia Store
  • Persistencia con useSessionStorage de VueUse
  • Control del tema a través de la API $q.dark de Quasar

2. Sistema de estilos (1 minuto)

// Definir variables de tema con SCSS Map
$themes: (
light: (
--bg-main: #fff,
--text: #000,
),
dark: (
--bg-main: #081f2d,
--text: #fff,
),
);

// Aplicar a :root y .body--dark
:root {
@include theme-vars('light');
}
.body--dark {
@include theme-vars('dark');
}
  • Componentes usan var(--bg-main) para cambio automático
  • Mixin @include light / @include dark para estilos complejos

3. Mecanismo de cambio (30 segundos)

const toggleTheme = () => {
$q.dark.toggle(); // Cambio Quasar
store.updateIsDarkMode($q.dark.isActive); // Sincronizar Store
};

4. Resultados reales (30 segundos)

  • Cambio fluido sin parpadeo (actualización dinámica de CSS Variables)
  • Estado persistente (el tema no se pierde al refrescar)
  • Fácil mantenimiento (gestión centralizada de variables de tema)
  • Alta eficiencia de desarrollo (Mixin simplifica desarrollo de estilos)"

Método de respuesta B: Solución general (alternativa)

"Para proyectos modernos recomiendo usar CSS Variables + Tailwind CSS:

1. Diseño de arquitectura (30 segundos)

  • Definir variables de tema con CSS Variables (colores, espaciado, sombras, etc.)
  • Cambiar el tema del elemento raíz a través del atributo data-theme
  • Combinar con la variante dark: de Tailwind para desarrollo rápido

2. Puntos de implementación (1 minuto)

:root {
--color-bg: #fff;
--color-text: #000;
}
[data-theme='dark'] {
--color-bg: #1f2937;
--color-text: #f9fafb;
}

Al cambiar con JavaScript solo se modifica el atributo data-theme, y el navegador aplica automáticamente las variables correspondientes.

3. Integración RWD (30 segundos)

<div class="text-sm md:text-base lg:text-lg dark:bg-gray-800"></div>

Se puede manejar RWD y cambio de tema simultáneamente.

4. Mejores prácticas (30 segundos)

  • Ejecutar inicialización del tema inmediatamente en <head> para evitar FOUC
  • Usar localStorage para guardar preferencia del usuario
  • Detectar prefers-color-scheme para seguir el tema del sistema"

Preguntas adicionales

Q1: Qué hacer si necesitas soportar IE?

A: Usar la solución de cambio de CSS Class, o usar el polyfill css-vars-ponyfill.

Q2: Cómo evitar el parpadeo al cambiar de tema?

A: Ejecutar el script inmediatamente en el HTML <head>, configurando el tema antes de que la página se renderice.

Q3: Cómo gestionar múltiples temas?

A: Se recomienda usar un sistema de Design Tokens para gestionar todas las variables de tema de forma unificada, sincronizado con Figma Variables.

Q4: Cómo probar diferentes temas?

A: Usar Storybook con storybook-addon-themes para probar visualmente todas las variantes de tema.


Temas relacionados

Reference