Módulo 1: What A Design System Is

De CSS ad-hoc a un sistema

Descripción

Ya sabes a dónde vamos: reemplazar decisiones sueltas por decisiones nombradas una vez. Esta lección hace ese salto concreto y lo mira de cerca. Vamos a tomar el mismo estilo —un espaciado que aparece una y otra vez en el storefront de Mercado— escrito de la forma en que casi todos empezamos (a mano, en cada pantalla) y verás, medido en píxeles reales, el drift que produce. Después lo resolveremos como lo resuelve un sistema, y el mismo modelo que midió el caos medirá el orden.

La pregunta que responde esta lección es incómoda a propósito: si cada pantalla se ve bien por separado, ¿por qué importa cómo estén escritos sus estilos? La respuesta es que "verse bien por separado" es una trampa. Una UI no es una pantalla; es veinte pantallas que un usuario recorre en secuencia, y que tres personas mantienen durante meses. La coherencia entre ellas —que el mismo espacio sea el mismo espacio en todas— no se nota cuando está, pero grita cuando falta. Y ese "que falta" no llega de golpe: se acumula, valor a valor, cada vez que alguien escribe 16px en una pantalla y 15px de memoria en otra.

Conexión con el módulo. La lección 1 te dio la tesis (un sistema reemplaza lo ad-hoc por lo nombrado) y la midió con un rol de color. Esta lección hace el mismo movimiento con un rol de espaciado, y sobre todo muestra el mecanismo del salto: qué significa exactamente pasar de "escribir el valor" a "referenciar el valor". Ese mecanismo —una decisión que vive en un lugar y se referencia desde muchos— es la definición operativa de un token, la primera de las cuatro capas que arma la lección 3. Aquí no construimos el token con su sintaxis real (eso es el módulo 2); construimos la intuición de por qué debe existir.

Una analogía: la paleta del pintor con colores ya mezclados

Imagina a un pintor frente a un mural de veinte escenas. Necesita que el cielo sea el mismo azul en las veinte.

El pintor ad-hoc mezcla el azul cada vez que lo necesita: un poco de este, una gota de aquel, "hasta que se vea igual al de ayer". La primera escena queda perfecta. La segunda, casi igual —pero ayer había otra luz y hoy la mezcla salió un pelín más clara—. Para la escena veinte, el cielo del principio y el del final ya no son el mismo azul, y nadie lo decidió: se fue corriendo, mezcla a mezcla. Peor aún: si el cliente pide "hagamos el cielo más profundo", el pintor tiene que volver a las veinte escenas y re-mezclar en cada una, esperando que le salga parejo.

El pintor con sistema mezcla el azul una vez, llena un frasco grande, le pega una etiqueta —"cielo"— y de ahí en adelante moja el pincel en el frasco. Las veinte escenas salen del mismo frasco, así que son, físicamente, el mismo azul: no puede haber drift, porque no hay veinte mezclas, hay una. Y si el cliente quiere el cielo más profundo, el pintor cambia el frasco —una vez— y las veinte escenas se actualizan solas la próxima vez que moje el pincel.

El frasco etiquetado es un token. El nombre en la etiqueta ("cielo", no "azul cobalto #3b82f6") es lo que hace que el frasco pueda cambiar de contenido sin mentir. Y el gesto de mojar el pincel en el frasco en vez de mezclar de nuevo es, exactamente, la diferencia entre referenciar y re-escribir. Todo este módulo cabe en esa imagen: deja de mezclar; usa el frasco.

De cerca: qué es "ad-hoc" y qué es "un sistema"

Vale la pena definir las dos palabras con precisión, porque toda la guía las usa.

Ad-hoc (del latín, "para esto") significa una solución hecha para el caso puntual, sin pensar en el conjunto. En CSS, un estilo ad-hoc es un valor escrito directamente donde se necesita, para esa pantalla, en ese momento:

/* En la pantalla de home, dentro del product-card */
.product-card { gap: 16px; }

/* En la pantalla de catálogo, otro día, de memoria */
.card { gap: 15px; }

/* En la pantalla de producto, otra persona */
.item { gap: 1rem; }

Cada línea es correcta. Cada una resuelve su caso. Ninguna sabe de las otras. Y esa ignorancia mutua es el problema: tres personas resolvieron "el espacio entre el nombre y el precio" tres veces, y les salió parecido pero no igual. El estilo ad-hoc optimiza el caso puntual a costa del conjunto.

Un sistema invierte la prioridad: optimiza el conjunto. En vez de escribir el valor donde se usa, se declara una vez, con un nombre que dice su rol, y se referencia:

/* Se declara UNA vez, en un lugar central. Esto es un token. */
:root {
  --space-4: 16px;   /* el "frasco etiquetado": el espacio estándar mediano */
}

/* Todas las pantallas lo REFERENCIAN, no lo re-escriben */
.product-card { gap: var(--space-4); }
.catalog-item { gap: var(--space-4); }
.product-item { gap: var(--space-4); }

La sintaxis de arriba (--space-4, var(...)) es CSS que ya viste en web-fundamentals —las custom properties— y que el módulo 2 convierte en un sistema de tokens completo. Aquí lo que importa no es la sintaxis; es el cambio de gesto: en la versión ad-hoc, el valor 16px está escrito tres veces (tres verdades); en la versión de sistema, está escrito una vez y referenciado tres (una verdad). Referenciar es mojar el pincel en el frasco.

Un detalle que confunde a mucha gente: un sistema no es "más código" ni "más complicado". La versión de sistema tiene menos decisiones (una, no tres) y menos valores mágicos sueltos. Lo que agrega es un nombre y una indirección —"no el 16px, sino --space-4 que vale 16px"—. Esa indirección es precisamente lo que te da el poder de cambiar el valor en un lugar. No es complejidad gratis; es la estructura mínima que hace posible el orden.

Ejemplo trabajado: el mismo espacio, medido en seis pantallas

El drift del espaciado es más traicionero que el del color, porque el CSS deja escribir la misma medida de varias formas: 16px, 1rem, 1.0rem son todos lo mismo cuando la base es 16px. Así que el caos se esconde: dos valores que se ven iguales pueden estar escritos distinto, y el día que algo cambie, se moverán por separado. Vamos a medirlo de verdad.

Tomamos "el gap entre el nombre y el precio" del product-card, tal como quedó escrito a mano en seis pantallas del storefront. Para comparar el valor visual y no solo el texto, primero normalizamos todo a píxeles (con 1rem = 16px), y luego contamos cuántos valores realmente distintos hay:

// L2 — modelo de drift: "el mismo espacio" entre el nombre y el precio del
// product-card, escrito a mano en 6 pantallas del storefront de Mercado.
const adHocGap = ['16px', '15px', '1rem', '18px', '16px', '0.9rem'];

// Normalizamos a pixeles para comparar de verdad (1rem = 16px).
function toPx(value) {
  if (value.endsWith('rem')) return Math.round(parseFloat(value) * 16);
  return parseInt(value, 10);
}
const asPx = adHocGap.map(toPx);
const distinctAdHoc = [...new Set(asPx)];

console.log('=== "El gap entre nombre y precio" en 6 pantallas ===\n');
console.log('Sin sistema (cada quien escribe el valor a mano):');
console.log('  valores escritos:   ' + adHocGap.join(', '));
console.log('  normalizados a px:  ' + asPx.join(', '));
console.log('  valores DISTINTOS para un mismo rol: ' + distinctAdHoc.length + '  -> caos');

// Con un sistema: un solo token de espaciado, space-4 = 16px.
const spacing = { 'space-4': 16 };
const withSystem = Array(6).fill(spacing['space-4']);
const distinctSystem = [...new Set(withSystem)];

console.log('\nCon un sistema (las 6 usan el token space-4):');
console.log('  valor aplicado:     ' + withSystem.map((v) => v + 'px').join(', '));
console.log('  valores DISTINTOS para un mismo rol: ' + distinctSystem.length + '  -> coherente');

Qué esperar. Al correr el archivo, la salida es exactamente esta:

=== "El gap entre nombre y precio" en 6 pantallas ===

Sin sistema (cada quien escribe el valor a mano):
  valores escritos:   16px, 15px, 1rem, 18px, 16px, 0.9rem
  normalizados a px:  16, 15, 16, 18, 16, 14
  valores DISTINTOS para un mismo rol: 4  -> caos

Con un sistema (las 6 usan el token space-4):
  valor aplicado:     16px, 16px, 16px, 16px, 16px, 16px
  valores DISTINTOS para un mismo rol: 1  -> coherente

Esta salida tiene una lección escondida que vale oro, así que léela con lupa.

Mira la línea normalizados a px: 16, 15, 16, 18, 16, 14. De los seis valores escritos, tres eran 16px "de verdad" (16px, 1rem, 16px → los tres dan 16), pero los otros tres eran 15, 18 y 14. La máquina contó 4 distintos. Fíjate en lo tramposo: si solo hubieras leído el texto original (16px, 15px, 1rem, 18px, 16px, 0.9rem), habrías dicho "hay como cinco valores raros". La normalización revela la verdad: el 1rem era en realidad idéntico a 16px —un falso problema— pero el 0.9rem (14px) y el 15px y el 18px sí eran distintos —problemas reales—. El drift del espaciado mezcla ruido (mismas medidas escritas distinto) con caos real (medidas de verdad distintas), y a ojo es imposible separarlos. Por eso un sistema no confía en el ojo: obliga a que todos escriban space-4, un solo nombre, un solo valor.

La segunda mitad es el mismo rol resuelto por un token: seis referencias a space-4, 1 valor distinto. No hubo que "elegir el mejor" de los cuatro valores ad-hoc ni convencer a nadie de usar 16px; simplemente todas apuntan al frasco etiquetado. El caos (4) se volvió coherencia (1) no por disciplina, sino porque estructuralmente ya no hay seis lugares donde escribir el valor: hay uno.

Una pregunta para que te la hagas antes de seguir: en la versión ad-hoc, ¿cuál de los cuatro valores es "el correcto"? La respuesta honesta es ninguno y cualquiera —nadie lo decidió—. Un sistema no responde "cuál es el correcto" descubriéndolo; lo responde eligiéndolo una vez y volviéndolo el único posible. Esa es la diferencia entre auditar el caos y prevenirlo.

Por qué el ad-hoc se siente más rápido (y por qué es una ilusión)

Si el sistema es tan superior, ¿por qué casi todos empezamos ad-hoc? Porque en el instante cero, escribir gap: 16px es más rápido que ir a declarar un token, nombrarlo y referenciarlo. El costo del ad-hoc no se paga al escribirlo; se paga después, y lo paga otro (o tú en seis meses). Es una deuda.

Vale la pena ver cuándo se cruza la línea. Con una pantalla, el ad-hoc gana: no hay conjunto que mantener coherente, el sistema es andamiaje sin edificio. Con dos o tres, empatan. A partir de cinco pantallas y dos personas, el ad-hoc ya está en números rojos: cada valor nuevo es una tirada de dados contra el drift, y cada cambio global es una cacería manual por todos los archivos. El storefront de Mercado tiene decenas de pantallas y evoluciona por meses: está profundamente del lado donde el sistema gana. La regla práctica: si algo se va a repetir en más de dos o tres lugares, o va a vivir más de una semana, nómbralo. Lo demás, ad-hoc está bien.

Errores comunes

Creer que un sistema es "sobre-ingeniería" para cualquier proyecto. Qué pasa: alguien lee sobre tokens y componentes y concluye "esto es para Google, yo tengo tres pantallas". Por qué pasa: se ve el costo de montar la estructura y no el costo —futuro, invisible— del drift que evita. Cómo detectarlo: estás copiando y pegando los mismos valores por tercera vez "porque total, son tres pantallas", y ya empezaron a divergir. Cómo corregirlo: el sistema escala hacia abajo. No necesitas un sistema enorme; necesitas no escribir el mismo valor tres veces. Un solo token de color y uno de espaciado ya son un sistema, y ya te ahorran drift. La pregunta no es "¿monto un design system?", sino "¿este valor se repite? Entonces nómbralo". Empiezas con lo que se repite y creces desde ahí.

Confundir "se ve igual" con "es lo mismo". Qué pasa: se aceptan 16px y 1rem mezclados "porque total, se ven idénticos". Por qué pasa: en la pantalla, con la base por defecto, son idénticos —el problema no es visible hoy—. Cómo detectarlo: lo viste ejecutado: 1rem y 16px normalizaron al mismo 16, pero eran dos representaciones distintas. El día que alguien cambie la base tipográfica, los rem se moverán y los px no, y aparecerá un drift que nadie tocó. Cómo corregirlo: un sistema no tiene dos formas de escribir la misma medida; tiene un token (space-4) con una representación. La coherencia no es solo que los valores se vean iguales hoy, sino que se muevan iguales mañana.

Sistematizar lo que no se repite. Qué pasa: en el afán de "hacerlo bien", se crea un token para un valor que se usa exactamente una vez —un margen puntual, un ancho específico de una sola pantalla—. Por qué pasa: se confunde "sistema" con "todo debe ser un token". Cómo detectarlo: tienes tokens con nombres tan específicos que solo tienen un uso (--margin-of-the-hero-on-the-landing), y el archivo de tokens es más largo que el CSS que ahorra. Cómo corregirlo: se nombra lo que se repite o representa un rol (el color de marca, el espacio estándar). Un valor genuinamente único puede quedarse ad-hoc sin culpa —no hay drift posible en algo que existe una sola vez—. El sistema es para lo compartido; forzarlo en lo único agrega indirección sin beneficio. El buen juicio es la mitad del oficio.

Ejercicios

Ejercicio 1 — Traduce ad-hoc a sistema. Toma este CSS ad-hoc de dos pantallas del storefront y reescríbelo usando un token de color y uno de espaciado, de modo que las dos referencien la misma decisión:

.home-banner  { background: #2563eb; padding: 24px; }
.sale-banner  { background: #2463ea; padding: 24px; }
Ver solución

Se declaran los tokens una vez y las dos pantallas los referencian:

:root {
  --color-primary: #2563eb;  /* el azul de marca, decidido una vez */
  --space-6: 24px;           /* el espacio grande de la escala */
}

.home-banner { background: var(--color-primary); padding: var(--space-6); }
.sale-banner { background: var(--color-primary); padding: var(--space-6); }

Las decisiones, una por una: los dos background eran #2563eb y #2463ea —un drift de color casi invisible— y ahora ambos son --color-primary, un solo valor. Los dos padding: 24px eran iguales por suerte, pero al escribirlos a mano nada garantizaba que siguieran iguales; ahora ambos son --space-6, atados al mismo frasco. Fíjate que no elegiste "cuál de los dos azules era el bueno": elegiste uno (#2563eb) y lo volviste el único. La sintaxis de los tokens la profundiza el módulo 2; aquí lo que importa es el gesto: declarar una vez, referenciar muchas.

Ejercicio 2 — Predice el conteo normalizado. Sin correr nada, di qué imprimiría el modelo del ejemplo (toPx + Set) como "valores DISTINTOS" para esta lista de un rol de espaciado: ['24px', '1.5rem', '24px', '20px', '1.5rem']. Recuerda: toPx convierte rem a píxeles con base 16.

Ver solución

Imprimiría 2.

Normalizando con toPx (1.5rem1.5 × 16 = 24): 24, 24, 24, 20, 24. Los valores distintos son 24 y 202.

El detalle: 1.5rem y 24px normalizan al mismo 24, así que las tres apariciones de "24" (dos como 24px, una... no, dos como 1.5rem y dos como 24px) colapsan en un solo valor. Solo el 20px rompe la coherencia. A ojo, la lista se veía como "tres formatos distintos y un valor raro"; la normalización revela que en realidad solo hay un valor rebelde (20px) escondido entre dos representaciones del mismo 24. Un token (space-6 = 24px) haría las cinco referencias idénticas y el conteo bajaría a 1.

Ejercicio 3 — ¿Nombrar o dejar ad-hoc? Para cada valor, decide si vale la pena convertirlo en un token del sistema o si puede quedarse ad-hoc, y justifica con la regla de "se repite / representa un rol":

  • (a) El color azul de todos los botones primarios, usado en 12 pantallas.
  • (b) Un margin-top: 3px de ajuste fino en un solo ícono de una sola pantalla.
  • (c) El espacio estándar entre tarjetas, usado en el catálogo, en home y en los resultados de búsqueda.
  • (d) El ancho exacto (437px) de una ilustración decorativa que solo aparece en la página "Acerca de".
Ver solución
  • (a) Token, sin duda. Se repite en 12 lugares y representa un rol claro (el color de marca / acción primaria). Es el caso de libro para un token: color.primary. Doce lugares ad-hoc son doce futuras fuentes de drift.
  • (b) Ad-hoc está bien. Un ajuste fino de 3px, en un solo ícono, en una sola pantalla, no se repite ni representa un rol compartido. Nombrarlo (--icon-nudge: 3px) agregaría indirección sin ahorrar nada. Déjalo ad-hoc.
  • (c) Token. El "espacio entre tarjetas" es un rol que aparece en tres pantallas; si cada una lo escribe a mano, divergirán. Sale de la escala de espaciado (space-4 o el que corresponda). Nómbralo.
  • (d) Ad-hoc está bien (con matiz). 437px para una ilustración única, en una sola página, no se repite. Un token no ayudaría. El matiz: si esa ilustración compartiera ancho con otras del mismo tipo, ahí sí valdría un token; pero como está, es un valor genuinamente único y puede quedarse.

La regla que se repite: nombra lo que se repite o representa un rol del sistema; deja ad-hoc lo genuinamente único. El sistema es para lo compartido, no para todo.

Resumen y siguiente paso

En esta lección hiciste concreto el salto de la lección 1: de CSS ad-hoc —el valor escrito a mano en cada lugar— a un sistema —el valor nombrado una vez y referenciado—. Con la paleta del pintor viste que el frasco etiquetado (el token) elimina el drift no por disciplina sino por estructura: las veinte escenas salen del mismo frasco, así que son el mismo azul. Y lo mediste ejecutando: "el mismo" gap escrito a mano en seis pantallas dio 4 valores distintos —con el 1rem revelándose como un falso problema y el 0.9rem como uno real—, y referenciado desde space-4 dio 1. Entendiste por qué el ad-hoc se siente más rápido (paga después, y lo paga otro), cuándo cruza la línea (más de dos o tres usos, más de una semana de vida), y que un sistema escala hacia abajo: no necesitas uno enorme, necesitas no escribir el mismo valor tres veces.

Antes de avanzar deberías poder: definir "ad-hoc" y "sistema" con precisión; explicar por qué "se ve igual" no es "es lo mismo"; y decidir, con la regla de "se repite / representa un rol", qué merece ser un token.

La lección 3 abre la estructura completa: las cuatro capas del sistema —tokens, utilidades, componentes y patrones—. Hasta aquí solo tocamos la capa más baja (los tokens, el frasco de pintura). Pero un sistema no es solo tokens: es cómo los tokens se convierten en utilidades, cómo las utilidades se ensamblan en componentes, y cómo los componentes se organizan en patrones. Vas a ver las cuatro capas, por qué cada una se construye sobre la anterior, y a ejecutar un inventario que las lista y cuenta las piezas de Mercado. Es el esqueleto sobre el que se cuelga todo lo demás.

Recursos