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​