Crear un sistema de temas dinámico (light/dark/brand) profesional

Tiempo de lectura: 2 minutos

Se va a crear un sistema de temas dinámico profesional con CSS variables para crear un tema oscuro, normal o de una marca.

Lago congelado - pexels
  1. Definir los Design Tokens base

Primero definimos la estructura de variables en :root.
No ponemos colores directos en componentes. Solo usamos variables semánticas.

:root {
  /* Colores semánticos */
  --color-bg: #ffffff;
  --color-surface: #f5f5f5;
  --color-primary: #1976d2;
  --color-text: #222222;
  --color-text-muted: #666666;

  /* Bordes */
  --radius-md: 8px;

  /* Sombras */
  --shadow-md: 0 4px 10px rgba(0, 0, 0, 0.1);
}

Clave profesional:
No llames a una variable --blue. Llámala --color-primary.
Así puedes cambiar el color sin cambiar el significado.

  1. Crear el tema dark

Ahora redefinimos solo las variables necesarias bajo una clase.

.theme-dark {
  --color-bg: #121212;
  --color-surface: #1e1e1e;
  --color-primary: #90caf9;
  --color-text: #ffffff;
  --color-text-muted: #bbbbbb;
  --shadow-md: 0 4px 10px rgba(0, 0, 0, 0.4);
}

Fíjate: no duplicamos estilos, solo cambiamos variables.

  1. Crear un tema de marca personalizado

Imagina que quieres un modo “startup verde” o una marca diferente.

.theme-brand-green {
  --color-primary: #2e7d32;
}

.theme-brand-purple {
  --color-primary: #6a1b9a;
}

Puedes combinar clases:

<body class="theme-dark theme-brand-purple">

Esto permite:
Modo oscuro + marca morada
Modo claro + marca verde
Combinaciones sin duplicar CSS

  1. Usar las variables en los componentes

Ejemplo real de estilos:

body {
  background-color: var(--color-bg);
  color: var(--color-text);
  transition: background-color 0.3s ease, color 0.3s ease;
}

.card {
  background-color: var(--color-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 16px;
}

.button {
  background-color: var(--color-primary);
  color: white;
  border: none;
  border-radius: var(--radius-md);
  padding: 10px 16px;
  cursor: pointer;
}

Observa que ningún componente sabe si está en dark o light.
Solo usa variables.

Eso es arquitectura limpia.

  1. Cambiar tema dinámicamente con JavaScript

Ahora lo interesante.

function setTheme(themeName) {
  document.body.className = themeName;
}

Botones de ejemplo:

<button onclick="setTheme('')">Light</button>
<button onclick="setTheme('theme-dark')">Dark</button>
<button onclick="setTheme('theme-dark theme-brand-purple')">
  Dark Purple
</button>
  1. Persistir la preferencia del usuario

Aquí es donde ya suena a producto real.

function setTheme(themeName) {
  document.body.className = themeName;
  localStorage.setItem("theme", themeName);
}

function loadTheme() {
  const savedTheme = localStorage.getItem("theme");
  if (savedTheme) {
    document.body.className = savedTheme;
  }
}

loadTheme();

Ahora el usuario vuelve y mantiene su tema.

  1. Detectar automáticamente el modo del sistema

Si quieres hacerlo nivel “empresa seria”:

function detectSystemTheme() {
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
  return prefersDark ? "theme-dark" : "";
}

function loadTheme() {
  const savedTheme = localStorage.getItem("theme");
  if (savedTheme) {
    document.body.className = savedTheme;
  } else {
    document.body.className = detectSystemTheme();
  }
}

loadTheme();

Esto hace que el primer render respete el sistema del usuario.

  1. Arquitectura recomendada en proyecto real

Estructura limpia:

styles
tokens.css
themes.css
components.css

tokens.css → variables base
themes.css → clases theme-dark, theme-brand
components.css → estilos usando var()

Separación clara. Escalable.

  1. Nivel avanzado: evitar “flash” al cargar

Problema clásico:
La página carga en light y luego cambia a dark.

Solución:
Inyectar el script antes del render:

<script>
  const savedTheme = localStorage.getItem("theme");
  if (savedTheme) {
    document.documentElement.className = savedTheme;
  }
</script>

Deja un comentario