Tabla de Contenidos
¡Esta es una revisión vieja del documento!
13. Sistema de diseño: Tokens
En el tema de BEM los componentes tenían los valores puestos directamente. En este tema vamos a hacer bien un componente usando tokens, que es lo mismo que hace Angular Material pero hecho por nosotros:
- Tokens de sistema: Variables CSS globales que usan todos los componentes. En nuestro sistema empiezan por
--mlt-sys-(como--mat-sys-en Angular Material). - Tokens de diseño: Variables CSS públicas de cada componente, por ejemplo
--boton-color-strong(como--mat-button-filled-container-coloren Angular Material). Su valor por defecto es un token de sistema.
Además, cada componente tiene variables privadas que no son tokens: son CSS que necesita el componente para funcionar y que no le interesan al diseñador.
Números mágicos
Veamos el estilo del botón que hicimos en Angular: Creación de componentes (boton.scss):
.boton {
font-family: sans-serif;
font-size: 16px;
padding: 6px;
border-radius: 6px;
border-width: 1px;
border-style: solid;
display: inline-block;
cursor: pointer;
text-decoration: none;
border-color: #0056b8;
background-color: #0056b8;
color: #ffffff;
}
Desgraciadamente tiene muchos números mágicos, es decir números puestos directamente. Para evitarlo se usan variables CSS (cómo se crean y se usan está en CSS: Básico, «Variables CSS»): los tokens.
Tokens de sistema
Los tokens de sistema son variables CSS globales con todos los valores que se pueden usar: colores, fuentes, tamaños, márgenes, etc. Así limitamos las opciones a una serie de valores, como vimos en el tema de diseño.
mlt (de Mislata) de la misma forma que mat es de Angular Material.
Y añadimos sys al igual que hace Angular Material para decir que son variable que se usan en todo el sistema ya que afectan a todos los componentes.
--mlt-sys-color-alternativo-5: En nuestro framework de clase en Mislata--mat-sys-secondary: En Angular Material
Los 9 tonos de cada color se han generado con el Color Scheme Generator:
:root {
--mlt-sys-color-principal-1: #011019;
--mlt-sys-color-principal-2: #052F4D;
--mlt-sys-color-principal-3: #0B4D83;
--mlt-sys-color-principal-4: #1363B4;
--mlt-sys-color-principal-5: #1979E6;
--mlt-sys-color-principal-6: #4B8EEC;
--mlt-sys-color-principal-7: #80ADF4;
--mlt-sys-color-principal-8: #B2CAFA;
--mlt-sys-color-principal-9: #E6EDFE;
--mlt-sys-color-alternativo-1: #190801;
--mlt-sys-color-alternativo-2: #4D1D05;
--mlt-sys-color-alternativo-3: #83370B;
--mlt-sys-color-alternativo-4: #B45613;
--mlt-sys-color-alternativo-5: #E67519;
--mlt-sys-color-alternativo-6: #EC9C4B;
--mlt-sys-color-alternativo-7: #F4BE80;
--mlt-sys-color-alternativo-8: #FADCB2;
--mlt-sys-color-alternativo-9: #FEF5E6;
--mlt-sys-color-rojo-1: #190501;
--mlt-sys-color-rojo-2: #4D0D05;
--mlt-sys-color-rojo-3: #83150B;
--mlt-sys-color-rojo-4: #B41813;
--mlt-sys-color-rojo-5: #E61919;
--mlt-sys-color-rojo-6: #EC534B;
--mlt-sys-color-rojo-7: #F48A80;
--mlt-sys-color-rojo-8: #FABCB2;
--mlt-sys-color-rojo-9: #FEEAE6;
--mlt-sys-color-gris-1: #1D2830;
--mlt-sys-color-gris-2: #323E49;
--mlt-sys-color-gris-3: #485461;
--mlt-sys-color-gris-4: #5C6775;
--mlt-sys-color-gris-5: #717D8E;
--mlt-sys-color-gris-6: #8A93A3;
--mlt-sys-color-gris-7: #A1A9BA;
--mlt-sys-color-gris-8: #B6BDCD;
--mlt-sys-color-gris-9: #CFD4E2;
--mlt-sys-color-blanco-1: #EBEBEB;
--mlt-sys-color-blanco-2: #EDEDED;
--mlt-sys-color-blanco-3: #F0F0F0;
--mlt-sys-color-blanco-4: #F2F2F2;
--mlt-sys-color-blanco-5: #F5F5F5;
--mlt-sys-color-blanco-6: #F7F7F7;
--mlt-sys-color-blanco-7: #FAFAFA;
--mlt-sys-color-blanco-8: #FCFCFC;
--mlt-sys-color-blanco-9: #FFFFFF;
--mlt-sys-color-amarillo-1: #6D5303;
--mlt-sys-color-amarillo-2: #9A7809;
--mlt-sys-color-amarillo-3: #C69C10;
--mlt-sys-color-amarillo-4: #E9BC1C;
--mlt-sys-color-amarillo-5: #ECC94B;
--mlt-sys-color-amarillo-6: #F2D978;
--mlt-sys-color-amarillo-7: #F7E7A6;
--mlt-sys-color-amarillo-8: #FCF4CF;
--mlt-sys-color-amarillo-9: #FFFFFF;
--mlt-sys-font-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mlt-sys-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--mlt-sys-font-serif: Palatino, "Palatino Linotype", "Book Antiqua", Georgia, "Times New Roman", Times, serif;
--mlt-sys-font-default: var(--mlt-sys-font-sans-serif);
--mlt-sys-font-size-1: 10px;
--mlt-sys-font-size-2: 12px;
--mlt-sys-font-size-3: 14px;
--mlt-sys-font-size-4: 16px;
--mlt-sys-font-size-5: 18px;
--mlt-sys-font-size-6: 20px;
--mlt-sys-font-size-7: 22px;
--mlt-sys-font-size-8: 24px;
--mlt-sys-font-size-9: 26px;
--mlt-sys-font-size-10: 28px;
--mlt-sys-font-size-11: 30px;
--mlt-sys-margin-0: 0px;
--mlt-sys-margin-1: 4px;
--mlt-sys-margin-2: 8px;
--mlt-sys-margin-3: 12px;
--mlt-sys-margin-4: 16px;
--mlt-sys-margin-5: 20px;
--mlt-sys-margin-6: 24px;
--mlt-sys-margin-7: 28px;
--mlt-sys-margin-8: 32px;
--mlt-sys-margin-9: 36px;
--mlt-sys-margin-10: 40px;
--mlt-sys-padding-0: 0px;
--mlt-sys-padding-1: 4px;
--mlt-sys-padding-2: 8px;
--mlt-sys-padding-3: 12px;
--mlt-sys-padding-4: 16px;
--mlt-sys-padding-5: 20px;
--mlt-sys-padding-6: 24px;
--mlt-sys-padding-7: 28px;
--mlt-sys-padding-8: 32px;
--mlt-sys-padding-9: 36px;
--mlt-sys-padding-10: 40px;
--mlt-sys-border-radius-1: 1px;
--mlt-sys-border-radius-2: 2px;
--mlt-sys-border-radius-3: 4px;
--mlt-sys-border-radius-4: 6px;
--mlt-sys-width-0: 0px;
--mlt-sys-width-1: 1px;
--mlt-sys-width-2: 4px;
--mlt-sys-width-3: 8px;
--mlt-sys-width-4: 12px;
--mlt-sys-width-5: 16px;
--mlt-sys-width-6: 20px;
--mlt-sys-width-7: 24px;
--mlt-sys-width-8: 28px;
--mlt-sys-width-9: 32px;
--mlt-sys-width-10: 36px;
--mlt-sys-width-11: 40px;
}
Esas variables se usarán siempre en vez de poner directamente números. El botón, ya con el nombre BEM del bloque (c-boton), queda así:
.c-boton {
font-family: var(--mlt-sys-font-default);
font-size: var(--mlt-sys-font-size-4);
padding: var(--mlt-sys-padding-3);
border-radius: var(--mlt-sys-border-radius-4);
border-width: var(--mlt-sys-width-1);
border-style: solid;
display: inline-block;
cursor: pointer;
text-decoration: none;
border-color: var(--mlt-sys-color-principal-5);
background-color: var(--mlt-sys-color-principal-5);
color: var(--mlt-sys-color-blanco-9);
}
Algunas de las variables de colores en la página principal de google, ¿te suena de algo?:
Tokens de diseño
Pero aun se tiene que mejorar la legibilidad del código. En vez de usar directamente los tokens de sistema, el botón crea sus propias variables:
.c-boton {
/* Parte pública: tokens de diseño */
--boton-color-strong: var(--mlt-sys-color-principal-5);
--boton-color-weak: var(--mlt-sys-color-blanco-9);
--boton-font-family: var(--mlt-sys-font-default);
--boton-font-size: var(--mlt-sys-font-size-4);
--boton-padding: var(--mlt-sys-padding-3);
--boton-border-width: var(--mlt-sys-width-1);
--boton-border-radius: var(--mlt-sys-border-radius-4);
/* Parte privada: estado del botón */
--boton-privateState-background-color: var(--boton-color-strong);
--boton-privateState-border-color: var(--boton-color-strong);
--boton-privateState-text-color: var(--boton-color-weak);
/* Propiedades CSS: solo usan variables del botón */
font-family: var(--boton-font-family);
font-size: var(--boton-font-size);
padding: var(--boton-padding);
border-radius: var(--boton-border-radius);
border-width: var(--boton-border-width);
border-style: solid;
display: inline-block;
cursor: pointer;
text-decoration: none;
border-color: var(--boton-privateState-border-color);
background-color: var(--boton-privateState-background-color);
color: var(--boton-privateState-text-color);
}
/* La función cambia un token de diseño: el color del botón */
.c-boton--funcion-normal {
--boton-color-strong: var(--mlt-sys-color-principal-5);
}
.c-boton--funcion-alternativa {
--boton-color-strong: var(--mlt-sys-color-alternativo-5);
}
.c-boton--funcion-peligrosa {
--boton-color-strong: var(--mlt-sys-color-rojo-5);
}
/* La importancia cambia el estado: qué color va en el fondo, el borde y el texto */
.c-boton--importancia-primaria {
--boton-privateState-background-color: var(--boton-color-strong);
--boton-privateState-border-color: var(--boton-color-strong);
--boton-privateState-text-color: var(--boton-color-weak);
}
.c-boton--importancia-secundaria {
--boton-privateState-background-color: var(--boton-color-weak);
--boton-privateState-border-color: var(--boton-color-strong);
--boton-privateState-text-color: var(--boton-color-strong);
}
.c-boton--importancia-terciaria {
--boton-privateState-background-color: var(--boton-color-weak);
--boton-privateState-border-color: var(--boton-color-weak);
--boton-privateState-text-color: var(--boton-color-strong);
}
<button class="c-boton c-boton--funcion-peligrosa c-boton--importancia-secundaria">Borrar</button>
Dentro del botón hay dos tipos de variables:
- Públicas (
--boton-*): Son los tokens de diseño del componente. Las decide el diseñador: el color del botón, la fuente, el tamaño, el redondeo… Su valor por defecto siempre es un token de sistema y se pueden cambiar desde fuera del componente.--boton-color-strong: El color del botón. Lo cambian los modificadores de función (normal, alternativa y peligrosa).--boton-color-weak: El otro color del botón, el blanco.
- Privadas (
--boton-privateState-*): Guardan el estado actual del botón: de qué color son ahora el fondo, el borde y el texto. No son tokens de diseño sino algo informático, el CSS que hace falta para implementar el botón. Las cambian los modificadores de importancia (primaria, secundaria y terciaria) a partir de--boton-color-strongy--boton-color-weak.
Al diseñador le interesa que el botón sea rojo y secundario; cómo se consigue eso con el fondo, el borde y el texto es cosa del programador. Por ejemplo, en la terciaria el borde se pone del mismo color que el fondo para que no se vea pero el botón no cambie de tamaño: eso es un truco de CSS, no una decisión de diseño.
| Pública | Privada | |
|---|---|---|
| Qué es | Token de diseño | Estado del componente |
| Nombre | --boton-* | --boton-privateState-* |
| Quién la cambia | El diseñador, el que usa el componente y los modificadores | Solo los modificadores del propio componente |
| Ejemplo | --boton-color-strong | --boton-privateState-background-color |
Las propiedades CSS solo usan variables del botón. Así se puede cambiar cómo está hecho el botón (sus variables privadas y sus propiedades) sin que cambie su parte pública.
privateState desde fuera del componente. Si para que un botón secundario sea verde redefines --boton-privateState-background-color, pintas de verde el fondo que debía ser blanco y el borde y el texto se quedan con el color anterior. Para cambiar el color se cambia el token: --boton-color-strong.
--boton-privatestate-text-color es otra variable y el botón no funcionaría.
Comparación con Angular Material
Nuestro sistema es una versión sencilla de lo que hace Angular Material:
| Angular Material | Nuestro sistema | |
|---|---|---|
| Token de sistema | --mat-sys-primary | --mlt-sys-color-principal-5 |
| Token de diseño | --mat-button-filled-container-color | --boton-color-strong |
| El token de diseño toma el valor de un token de sistema | var(--mat-button-filled-container-color, var(--mat-sys-primary)) | --boton-color-strong: var(--mlt-sys-color-principal-5) |
| Cambiar un componente | mixin mat.button-overrides(…) | redefinir sus tokens de diseño --boton-* (nunca los privateState) |
| Cambiar todo el sistema | el tema (mat.theme) | cambiar las variables de SASS que generan las --mlt-sys-* |
| Variantes del componente | matButton="filled", "outlined", "text" | modificadores --importancia-primaria, -secundaria, -terciaria |
Mejorando los tokens con SASS
Tokens de sistema
Escribir a mano todos los tokens de sistema es largo y es fácil equivocarse. Con SASS se guardan los valores en listas y mapas y los tokens se generan con bucles. Así, para cambiar el sistema solo hay que cambiar las variables de SASS. Los colores se generan igual que en el ejercicio 1 de SASS, ahora con el prefijo --mlt-sys-color-.
@use "sass:list";
$colores: (
'principal': (#011019, #052F4D, #0B4D83, #1363B4, #1979E6, #4B8EEC, #80ADF4, #B2CAFA, #E6EDFE),
'alternativo': (#190801, #4D1D05, #83370B, #B45613, #E67519, #EC9C4B, #F4BE80, #FADCB2, #FEF5E6),
'rojo': (#190501, #4D0D05, #83150B, #B41813, #E61919, #EC534B, #F48A80, #FABCB2, #FEEAE6),
'gris': (#1D2830, #323E49, #485461, #5C6775, #717D8E, #8A93A3, #A1A9BA, #B6BDCD, #CFD4E2),
'blanco': (#EBEBEB, #EDEDED, #F0F0F0, #F2F2F2, #F5F5F5, #F7F7F7, #FAFAFA, #FCFCFC, #FFFFFF),
'amarillo': (#6D5303, #9A7809, #C69C10, #E9BC1C, #ECC94B, #F2D978, #F7E7A6, #FCF4CF, #FFFFFF)
);
$font-sizes: (10px, 12px, 14px, 16px, 18px, 20px, 22px, 24px, 26px, 28px, 30px);
:root {
@each $nombre, $tonos in $colores {
@for $i from 1 through list.length($tonos) {
--mlt-sys-color-#{$nombre}-#{$i}: #{list.nth($tonos, $i)};
}
}
@for $i from 1 through list.length($font-sizes) {
--mlt-sys-font-size-#{$i}: #{list.nth($font-sizes, $i)};
}
}
se transforma en
:root {
--mlt-sys-color-principal-1: #011019;
--mlt-sys-color-principal-2: #052F4D;
...
--mlt-sys-color-amarillo-9: #FFFFFF;
--mlt-sys-font-size-1: 10px;
...
--mlt-sys-font-size-11: 30px;
}
El resto de tokens de sistema (márgenes, paddings, fuentes…) se generan igual. Es lo que se pide en los ejercicios.
Tokens de diseño
El botón con sus modificadores, con el selector padre &:
.c-boton {
// Parte pública: tokens de diseño
--boton-color-strong: var(--mlt-sys-color-principal-5);
--boton-color-weak: var(--mlt-sys-color-blanco-9);
--boton-font-family: var(--mlt-sys-font-default);
--boton-font-size: var(--mlt-sys-font-size-4);
--boton-padding: var(--mlt-sys-padding-3);
--boton-border-width: var(--mlt-sys-width-1);
--boton-border-radius: var(--mlt-sys-border-radius-4);
// Parte privada: estado del botón
--boton-privateState-background-color: var(--boton-color-strong);
--boton-privateState-border-color: var(--boton-color-strong);
--boton-privateState-text-color: var(--boton-color-weak);
// Propiedades CSS: solo usan variables del botón
font-family: var(--boton-font-family);
font-size: var(--boton-font-size);
padding: var(--boton-padding);
border-radius: var(--boton-border-radius);
border-width: var(--boton-border-width);
border-style: solid;
display: inline-block;
cursor: pointer;
text-decoration: none;
border-color: var(--boton-privateState-border-color);
background-color: var(--boton-privateState-background-color);
color: var(--boton-privateState-text-color);
// La función cambia un token de diseño
&--funcion-normal {
--boton-color-strong: var(--mlt-sys-color-principal-5);
}
&--funcion-alternativa {
--boton-color-strong: var(--mlt-sys-color-alternativo-5);
}
&--funcion-peligrosa {
--boton-color-strong: var(--mlt-sys-color-rojo-5);
}
// La importancia cambia el estado
&--importancia-primaria {
--boton-privateState-background-color: var(--boton-color-strong);
--boton-privateState-border-color: var(--boton-color-strong);
--boton-privateState-text-color: var(--boton-color-weak);
}
&--importancia-secundaria {
--boton-privateState-background-color: var(--boton-color-weak);
--boton-privateState-border-color: var(--boton-color-strong);
--boton-privateState-text-color: var(--boton-color-strong);
}
&--importancia-terciaria {
--boton-privateState-background-color: var(--boton-color-weak);
--boton-privateState-border-color: var(--boton-color-weak);
--boton-privateState-text-color: var(--boton-color-strong);
}
}
Ejercicios
Ejercicio 1
Dadas las listas:
$paddings: (0px, 4px, 8px, 12px, 16px, 20px, 24px, 28px, 32px, 36px, 40px); $margins: (0px, 4px, 8px, 12px, 16px, 20px, 24px, 28px, 32px, 36px, 40px);
Genera los tokens de sistema de paddings y márgenes. Empiezan en 0:
:root {
--mlt-sys-padding-0: 0px;
--mlt-sys-padding-1: 4px;
...
--mlt-sys-padding-10: 40px;
--mlt-sys-margin-0: 0px;
...
--mlt-sys-margin-10: 40px;
}
Ejercicio 2
Dado el mapa:
$fuentes: ( 'sans-serif': (-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif), 'serif': (Palatino, "Palatino Linotype", "Book Antiqua", Georgia, "Times New Roman", Times, serif), 'monospace': (SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace) );
Genera los tokens de sistema de las fuentes y el de la fuente por defecto, que es la sans-serif:
:root {
--mlt-sys-font-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mlt-sys-font-serif: Palatino, "Palatino Linotype", "Book Antiqua", Georgia, "Times New Roman", Times, serif;
--mlt-sys-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--mlt-sys-font-default: var(--mlt-sys-font-sans-serif);
}
Ejercicio 3
Junta en un único fichero SASS todas las variables de SASS del sistema (colores, tamaños de fuente, márgenes, paddings, fuentes, border-radius y anchos) y genera con él todo el :root de los tokens de sistema del tema.
Cambia ahora el color principal por un verde (genera sus 9 tonos con el Color Scheme Generator) y comprueba que el botón cambia de color sin tocar su CSS.

