Actualizado 11 de julio de 2026

Tokens semánticos para el cambio de tema

El cambio de tema es un problema de nomenclatura antes de ser un problema de CSS. Los design tokens de modo oscuro son roles semánticos — background, surface, text-primary — donde cada rol contiene dos valores, uno por tema, y los componentes solo hacen referencia al nombre del rol. Cambia el tema y cada referencia se vuelve a resolver de golpe; ningún componente tiene una opinión sobre en qué modo está. El riesgo de ingeniería no es el cambio sino el mapeo: la tabla de rol-a-valor de un tema oscuro no es un espejo de la del claro, así que hay que derivarla y volver a comprobarla, nunca invertirla.

La configuración por capas que hay debajo cabe en un párrafo: los tokens primitivos nombran la materia prima (los pasos de la rampa — brand-600, neutral-100), los tokens semánticos nombran trabajos de la UI y apuntan a los primitivos, y los componentes consumen solo semántica. Los argumentos de nomenclatura y la mecánica del archivo de diseño tienen sus propias guías; este artículo — parte de nuestra guía de modo oscuro — trata sobre lo que hace la capa semántica cuando llega un segundo tema.

¿Por qué cada rol necesita dos valores?

Porque el significado de un rol es estable y su valor no lo es. background significa «el color en reposo de la página» en cualquier tema; el valor que hace ese trabajo es casi blanco en modo claro y casi negro en oscuro. La capa semántica es la frontera del tema: todo lo que hay por encima — componentes, layouts, pantallas — permanece ciego al tema, y todo lo que hay por debajo cambia por completo.

Esta es también la razón intuitiva por la que el patrón funciona para quienes lo usan. Un diseñador o desarrollador que piensa en roles se pregunta para qué sirve esta superficie, lo que tiene una sola respuesta; pensar en valores pregunta qué gris es este, lo que tiene dos respuestas que hay que mantener sincronizadas a mano. Los nombres que prometen un trabajo en lugar de un valor son los que hacen que el segundo tema sea un cambio de datos y no un segundo diseño.

¿Por qué el mapeo oscuro no es un espejo del claro?

El modelo mental tentador es invertir la escala — si el modo claro usa neutral-100 para la página, el modo oscuro usa su número opuesto. Derivar el mapeo y comprobarlo produce algo más interesante. Seis roles de una paleta sembrada con #2563eboklch(0.546 0.215 262.9):

RolClaroOscuro (derivado)Lo que diría un espejo directo
backgroundneutral-100 (L ≈0,94)neutral-900 (L ≈0,22)neutral-800 — pero la página se ancla en la parte inferior de la escala, así que la escalera de elevación tiene espacio por encima
surface (tarjeta)neutral-50 (L ≈0,97)neutral-800 (L ≈0,28)neutral-900 — que hundiría la tarjeta por debajo de la página e invertiría la elevación
text-primaryneutral-800 (L ≈0,28)neutral-100 (L ≈0,94)neutral-100 — se mantiene, aunque los temas ajustados a mano a menudo lo suavizan de más hasta neutral-200, que se lee endeble
text-secondaryneutral-600 (L ≈0,47)neutral-300 (L ≈0,78)neutral-300 — el espejo se mantiene
border-subtleneutral-200, más oscuro que la tarjetaneutral-700, más claro que la tarjetalos mismos pasos — pero la relación invierte su dirección
accentbrand-600 (≈#3E65B5)oklch(0.70 0.14 262.9) de la rampa oscurabrand-300 de la rampa clara — la rampa equivocada por completo

Vale la pena nombrar tres asimetrías. Primera, background y surface cruzan sus espejos: las superficies elevadas deben ser más claras que aquello sobre lo que se apoyan en modo oscuro, así que la tarjeta acaba por encima de la página en la escala de luminosidad aunque el espejo la habría colocado debajo. Segunda, el texto: neutral-200 sobre la página oscura todavía supera el suelo aritmético (≈12:1 frente al requisito de 4,5:1), pero el texto claro sobre oscuro tiende a renderizarse más endeble de lo que sugieren los números — APCA, que modela la polaridad, reporta el par más bajo que su gemelo del tema claro — así que la derivación acaba un paso más clara de lo que la cautela elegiría. Tercera, el valor oscuro del acento no proviene en absoluto de la rampa clara: una paleta oscura derivada recorre su propia curva de luminosidad con el croma reforzado en aproximadamente un 20 %, porque los entornos oscuros atenúan la coloridad percibida.

La consecuencia práctica: los dos mapeos deberían producirse a partir de una sola derivación, no mantenerse como dos listas. La exportación de tokens de Scale Composer lleva una sección semantic y una semantic-dark generadas a partir de las mismas semillas — cada rol vuelve a derivarse por tema, los suelos WCAG vuelven a comprobarse, APCA se reporta junto a ellos, y los roles onFill vuelven a verificarse, ya que un relleno que llevaba texto blanco en claro puede querer texto oscuro en oscuro.

Abre la exportación con ambas secciones semánticas en Scale Composer — los mismos nombres de rol en ambos lados, los valores oscuros que visiblemente no son un espejo de los claros.

Exportación de tokens que muestra las secciones semantic y semantic-dark una al lado de la otra, con los mismos nombres de rol resolviéndose a valores distintos y asimétricos

¿Qué patrones de cambio de tema fallan a escala?

Se repiten tres patrones, y los tres fallan de la misma manera — el mapeo se dispersa en lugar de declararse una sola vez.

Variantes dark: por componente. El prefijo dark: de Tailwind repite el valor oscuro en cada sitio de uso: bg-white dark:bg-gray-900 en cada tarjeta, en cada archivo. Para un sitio pequeño esto está bien — todo el mapeo cabe en una pantalla. Para un sistema es un impuesto de mantenimiento: cambiar lo que significa «surface» en modo oscuro es un buscar-y-reemplazar por toda la base de código, y cualquier componente al que le faltó su gemelo dark: sale roto en uno de los temas.

if (isDark) en el código del componente. Los condicionales de tema mueven el mapeo hacia la lógica, donde puede ramificarse, desviarse y escapar a la revisión. Un componente que pregunta qué tema está activo ha asumido una responsabilidad que la capa de tokens ya lleva.

Dos hojas de estilo. Un dark.css mantenido junto a un light.css empieza como una copia y se desvía desde la primera edición que toca solo uno de los archivos. Nada obliga a que ambos archivos respondan a las mismas preguntas.

¿Cómo llevan el cambio las propiedades personalizadas de CSS?

Volviendo a declarar los mismos nombres bajo un ámbito de tema. Los componentes hacen referencia a la propiedad personalizada; el valor de la propiedad depende de qué declaración se aplica en cada momento — la mecánica son las propiedades personalizadas de CSS más la cascada, nada más.

:root {
  --color-background:   oklch(0.94 0.008 262.9);  /* neutral-100 */
  --color-surface:      oklch(0.97 0.005 262.9);  /* neutral-50  */
  --color-text-primary: oklch(0.28 0.02 262.9);   /* neutral-800 */
  --color-accent:       oklch(0.546 0.215 262.9); /* núcleo de marca */
}

[data-theme="dark"] {
  --color-background:   oklch(0.22 0.015 262.9);  /* neutral-900 */
  --color-surface:      oklch(0.28 0.02 262.9);   /* neutral-800 */
  --color-text-primary: oklch(0.94 0.008 262.9);  /* neutral-100 */
  --color-accent:       oklch(0.70 0.14 262.9);   /* rampa oscura */
}

.card {
  background: var(--color-surface);
  color: var(--color-text-primary);
}

La regla .card es el quid: aparece una vez, no menciona ningún tema y es correcta en ambos. El bloque oscuro puede igualmente colgar de la media query prefers-color-scheme, o — el patrón robusto — la query fija el valor por defecto y el atributo lo sobrescribe; los detalles del cableado tienen su propio artículo. Sea cual sea el disparador que elijas, la exportación de CSS desde una fuente de tokens genera ambos bloques a partir de la misma derivación, que es lo que impide que los dos mapeos se desvíen entre sí.

Observa cómo se resuelve un cambio

Es más fácil confiar en el mapeo después de verlo en movimiento. Cambia la vista previa del tema sobre el conjunto completo de roles — cada rol semántico volviéndose a resolver de golpe, con las asimetrías de la tabla anterior visibles en su sitio: la tarjeta que se mantiene más clara que la página, el acento que salta a un paso más claro de una rampa re-derivada, sin decisiones a nivel de componente en ninguna parte.

Seguir leyendo

  • El modo oscuro no es inversión

    El diseño de modo oscuro empieza donde falla la inversión: lógica de rol volteada, elevación rota, color drenado. Qué significa derivar una paleta oscura de las mismas semillas.

  • prefers-color-scheme: cómo usarlo bien en CSS

    Cómo funciona prefers-color-scheme: la media query como valor por defecto, una anulación con data-theme para el interruptor manual, color-scheme y el arreglo del destello del tema equivocado.