Una guía de estrategia para las propiedades personalizadas de CSS

Publicado: 2022-03-10
Resumen rápido ↬ Las propiedades dinámicas brindan oportunidades para nuevas ideas creativas, pero también el potencial para agregar complejidad a CSS. Para aprovecharlos al máximo, es posible que necesitemos una estrategia sobre cómo escribimos y estructuramos CSS con propiedades personalizadas.

Las propiedades personalizadas de CSS (a veces conocidas como 'variables de CSS') ahora son compatibles con todos los navegadores modernos y la gente está comenzando a usarlas en producción. Esto es genial, pero son diferentes de las variables en los preprocesadores, y ya he visto muchos ejemplos de personas que los usan sin considerar las ventajas que ofrecen.

Las propiedades personalizadas tienen un gran potencial para cambiar la forma en que escribimos y estructuramos CSS y, en menor medida, la forma en que usamos JavaScript para interactuar con los componentes de la interfaz de usuario. No me voy a centrar en la sintaxis y cómo funcionan (para eso te recomiendo leer “Es hora de empezar a usar propiedades personalizadas”). En su lugar, quiero profundizar en las estrategias para aprovechar al máximo las propiedades personalizadas de CSS.

¿En qué se parecen a las variables de los preprocesadores?

Las propiedades personalizadas son un poco como las variables en los preprocesadores, pero tienen algunas diferencias importantes. La primera y más obvia diferencia es la sintaxis.

Con SCSS usamos un símbolo de dólar para denotar una variable:

 $smashing-red: #d33a2c;

En Less usamos un símbolo @ :

 @smashing-red: #d33a2c;

Las propiedades personalizadas siguen convenciones similares y usan un prefijo -- :

 :root { --smashing-red: #d33a2c; } .smashing-text { color: var(--smashing-red); }

Una diferencia importante entre las propiedades personalizadas y las variables en los preprocesadores es que las propiedades personalizadas tienen una sintaxis diferente para asignar un valor y recuperar ese valor. Al recuperar el valor de una propiedad personalizada, usamos la función var() .

¡Más después del salto! Continúe leyendo a continuación ↓

La siguiente diferencia más obvia está en el nombre. Se llaman 'propiedades personalizadas' porque realmente son propiedades CSS. En los preprocesadores, puede declarar y usar variables casi en cualquier lugar, incluidos los bloques de declaración externos, las reglas de medios o incluso como parte de un selector.

 $breakpoint: 800px; $smashing-red: #d33a2c; $smashing-things: ".smashing-text, .cats"; @media screen and (min-width: $breakpoint) { #{$smashing-things} { color: $smashing-red; } }

La mayoría de los ejemplos anteriores no serían válidos si se usaran propiedades personalizadas.

Las propiedades personalizadas tienen las mismas reglas sobre dónde se pueden usar como propiedades CSS normales. Es mucho mejor pensar en ellas como propiedades dinámicas que como variables. Eso significa que solo se pueden usar dentro de un bloque de declaración o, en otras palabras, las propiedades personalizadas están vinculadas a un selector. Puede ser el selector :root o cualquier otro selector válido.

 :root { --smashing-red: #d33a2c; } @media screen and (min-width: 800px) { .smashing-text, .cats { --margin-left: 1em; } }

Puede recuperar el valor de una propiedad personalizada en cualquier lugar donde de otro modo usaría un valor en una declaración de propiedad. Esto significa que se pueden usar como un solo valor, como parte de una declaración abreviada o incluso dentro de ecuaciones calc() .

 .smashing-text, .cats { color: var(--smashing-red); margin: 0 var(--margin-horizontal); padding: calc(var(--margin-horizontal) / 2) }

Sin embargo, no se pueden usar en consultas de medios o selectores que incluyan :nth-child() .

Probablemente haya mucho más que desee saber sobre la sintaxis y cómo funcionan las propiedades personalizadas, por ejemplo, cómo usar valores alternativos y si puede asignar variables a otras variables (sí), pero esta introducción básica debería ser suficiente para comprender el resto de los conceptos de este artículo. Para obtener más información sobre los detalles de cómo funcionan las propiedades personalizadas, puede leer "Es hora de comenzar a usar propiedades personalizadas", escrito por Serg Hospodarets.

Dinámico vs Estático

Dejando a un lado las diferencias cosméticas, la diferencia más significativa entre las variables en los preprocesadores y las propiedades personalizadas es cómo se delimitan. Podemos referirnos a las variables como de ámbito estático o dinámico. Las variables en los preprocesadores son estáticas, mientras que las propiedades personalizadas son dinámicas.

En lo que respecta a CSS, estático significa que puede actualizar el valor de una variable en diferentes puntos del proceso de compilación, pero esto no puede cambiar el valor del código anterior.

 $background: blue; .blue { background: $background; } $background: red; .red { background: $background; }

resultados en:

 .blue { background: blue; } .red { background: red; }

Una vez que esto se representa en CSS, las variables desaparecen. Esto significa que potencialmente podríamos leer un archivo .scss y determinar su salida sin saber nada sobre el HTML, el navegador u otras entradas. Este no es el caso con las propiedades personalizadas.

Los preprocesadores tienen una especie de "alcance de bloque" donde las variables se pueden cambiar temporalmente dentro de un selector, función o mezcla. Esto cambia el valor de una variable dentro del bloque, pero sigue siendo estático. Esto está ligado al bloque, no al selector. En el siguiente ejemplo, la variable $background se cambia dentro del bloque .example . Vuelve al valor inicial fuera del bloque, incluso si usamos el mismo selector.

 $background: red; .example { $background: blue; background: $background; } .example { background: $background; }

Esto dará como resultado:

 .example { background: blue; } .example { background: red; }

Las propiedades personalizadas funcionan de manera diferente. En lo que respecta a las propiedades personalizadas, el alcance dinámico significa que están sujetas a la herencia y la cascada. La propiedad está vinculada a un selector y si el valor cambia, esto afecta a todos los elementos DOM coincidentes como cualquier otra propiedad CSS.

Esto es excelente porque puede cambiar el valor de una propiedad personalizada dentro de una consulta de medios, con un pseudoselector como pasar el mouse por encima o incluso con JavaScript.

 a { --link-color: black; } a:hover, a:focus { --link-color: tomato; } @media screen and (min-width: 600px) { a { --link-color: blue; } } a { color: var(--link-color); }

No tenemos que cambiar dónde se usa la propiedad personalizada: cambiamos el valor de la propiedad personalizada con CSS. Esto significa que al usar la misma propiedad personalizada, podemos tener diferentes valores en diferentes lugares o contextos en la misma página.

Global frente a local

Además de ser estáticas o dinámicas, las variables también pueden ser globales o locales. Si escribe JavaScript, estará familiarizado con esto. Las variables se pueden aplicar a todo lo que hay dentro de una aplicación o su alcance se puede limitar a funciones o bloques de código específicos.

CSS es similar. Tenemos algunas cosas que se aplican globalmente y algunas cosas que son más locales. Los colores de la marca, el espaciado vertical y la tipografía son ejemplos de cosas que quizás desee que se apliquen global y consistentemente en su sitio web o aplicación. También tenemos cosas locales. Por ejemplo, un componente de botón puede tener una variante pequeña y otra grande. No querrá que los tamaños de estos botones se apliquen a todos los elementos de entrada o incluso a todos los elementos de la página.

Esto es algo con lo que estamos familiarizados en CSS. Hemos desarrollado sistemas de diseño, convenciones de nomenclatura y bibliotecas de JavaScript, todo para ayudar a aislar los componentes locales y los elementos de diseño globales. Las propiedades personalizadas brindan nuevas opciones para tratar este viejo problema.

Las propiedades personalizadas de CSS tienen un alcance local predeterminado para los selectores específicos a los que las aplicamos. Así que son como variables locales. Sin embargo, las propiedades personalizadas también se heredan, por lo que en muchas situaciones se comportan como variables globales, especialmente cuando se aplican al selector :root . Esto significa que debemos ser reflexivos acerca de cómo usarlos.

Muchos ejemplos muestran propiedades personalizadas que se aplican al elemento :root y, aunque esto está bien para una demostración, puede resultar en un alcance global desordenado y problemas no deseados con la herencia. Afortunadamente, ya hemos aprendido estas lecciones.

Las variables globales tienden a ser estáticas

Hay algunas pequeñas excepciones, pero en términos generales, la mayoría de las cosas globales en CSS también son estáticas.

Las variables globales como los colores de la marca, la tipografía y el espaciado no tienden a cambiar mucho de un componente a otro. Cuando cambian, esto tiende a ser un cambio de marca global o algún otro cambio significativo que rara vez ocurre en un producto maduro. Todavía tiene sentido que estas cosas sean variables, se usan en muchos lugares y las variables ayudan con la consistencia. Pero no tiene sentido que sean dinámicos. El valor de estas variables no cambia de forma dinámica.

Por esta razón, recomiendo enfáticamente usar preprocesadores para variables globales (estáticas). Esto no solo asegura que siempre sean estáticos, sino que los denota visualmente dentro del código. Esto puede hacer que CSS sea mucho más legible y fácil de mantener.

Las variables estáticas locales están bien (a veces)

Podría pensar que, dada la firme postura de que las variables globales son estáticas, por reflexión, todas las variables locales podrían necesitar ser dinámicas. Si bien es cierto que las variables locales tienden a ser dinámicas, esto no es tan fuerte como la tendencia de una variable global a ser estática.

Las variables estáticas localmente están perfectamente bien en muchas situaciones. Utilizo variables de preprocesadores en archivos de componentes principalmente para conveniencia del desarrollador.

Considere el ejemplo clásico de un componente de botón con múltiples variaciones de tamaño.

botones

Mi scss podría verse así:

 $button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { // Visual styles } .btn-sml { font-size: $button-sml; } .btn-med { font-size: $button-med; } .btn-lrg { font-size: $button-lrg; }

Obviamente, este ejemplo tendría más sentido si estuviera usando las variables varias veces o derivando valores de margen y relleno de las variables de tamaño. Sin embargo, la capacidad de crear rápidamente prototipos de diferentes tamaños podría ser una razón suficiente.

Debido a que la mayoría de las variables estáticas son globales, me gusta diferenciar las variables estáticas que se usan solo dentro de un componente. Para hacer esto, puede prefijar estas variables con el nombre del componente, o puede usar otro prefijo como c-variable-name para componente o l-variable-name para local. Puede usar cualquier prefijo que desee, o puede prefijar variables globales. Independientemente de lo que elija, es útil diferenciarlo, especialmente si convierte una base de código existente para usar propiedades personalizadas.

Cuándo usar propiedades personalizadas

Si está bien usar variables estáticas dentro de los componentes, ¿cuándo deberíamos usar propiedades personalizadas? La conversión de variables de preprocesador existentes en propiedades personalizadas generalmente tiene poco sentido. Después de todo, el motivo de las propiedades personalizadas es completamente diferente. Las propiedades personalizadas tienen sentido cuando tenemos propiedades CSS que cambian en relación con una condición en el DOM, especialmente una condición dinámica como :focus , :hover , media queries o con JavaScript.

Sospecho que siempre usaremos algún tipo de variables estáticas, aunque es posible que necesitemos menos en el futuro, ya que las propiedades personalizadas ofrecen nuevas formas de organizar la lógica y el código. Hasta entonces, creo que en la mayoría de las situaciones trabajaremos con una combinación de variables de preprocesador y propiedades personalizadas.

Es útil saber que podemos asignar variables estáticas a propiedades personalizadas. Ya sean globales o locales, tiene sentido en muchas situaciones convertir variables estáticas en propiedades personalizadas dinámicas localmente.

Nota : ¿Sabía que $var es un valor válido para una propiedad personalizada? Las versiones recientes de Sass reconocen esto y, por lo tanto, necesitamos interpolar variables asignadas a propiedades personalizadas, como esta: #{$var} . Esto le dice a Sass que desea generar el valor de la variable, en lugar de solo $var en la hoja de estilo. Esto solo es necesario para situaciones como propiedades personalizadas, donde los nombres de variables también pueden ser un CSS válido.

Si tomamos el ejemplo del botón anterior y decidimos que todos los botones deben usar la pequeña variación en los dispositivos móviles, independientemente de la clase aplicada en el HTML, esta es ahora una situación más dinámica. Para esto, debemos usar propiedades personalizadas.

 $button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { --button-size: #{$button-sml}; } @media screen and (min-width: 600px) { .btn-med { --button-size: #{$button-med}; } .btn-lrg { --button-size: #{$button-lrg}; } } .btn { font-size: var(--button-size); }

Aquí creo una única propiedad personalizada: --button-size . Esta propiedad personalizada se aplica inicialmente a todos los elementos de botón que utilizan la clase btn . Luego cambio el valor de --button-size por encima de 600px para las clases btn-med y btn-lrg . Finalmente, aplico esta propiedad personalizada a todos los elementos de botón en un solo lugar.

No seas demasiado inteligente

La naturaleza dinámica de las propiedades personalizadas nos permite crear algunos componentes inteligentes y complicados.

Con la introducción de los preprocesadores, muchos de nosotros creamos bibliotecas con abstracciones inteligentes usando mixins y funciones personalizadas. En casos limitados, ejemplos como este siguen siendo útiles hoy en día, pero en su mayor parte, cuanto más tiempo trabajo con preprocesadores, menos funciones uso. Hoy, uso preprocesadores casi exclusivamente para variables estáticas.

Las propiedades personalizadas no serán (y no deberían) ser inmunes a este tipo de experimentación, y espero ver muchos ejemplos ingeniosos. Pero a la larga, el código legible y mantenible siempre ganará a las abstracciones inteligentes (al menos en producción).

Recientemente leí un excelente artículo sobre este tema en Free Code Camp Medium. Fue escrito por Bill Sourour y se llama “Don't Do It At Runtime. Hágalo en tiempo de diseño”. En lugar de parafrasear sus argumentos, te dejaré leerlo.

Una diferencia clave entre las variables del preprocesador y las propiedades personalizadas es que las propiedades personalizadas funcionan en tiempo de ejecución. Esto significa que cosas que podrían haber estado en el límite de lo aceptable, en términos de complejidad, con preprocesadores podrían no ser una buena idea con propiedades personalizadas.

Un ejemplo que me ilustró esto recientemente fue este:

 :root { --font-scale: 1.2; --font-size-1: calc(var(--font-scale) * var(--font-size-2)); --font-size-2: calc(var(--font-scale) * var(--font-size-3)); --font-size-3: calc(var(--font-scale) * var(--font-size-4)); --font-size-4: 1rem; }

Esto genera una escala modular. Una escala modular es una serie de números que se relacionan entre sí mediante una razón. A menudo se utilizan en el diseño y desarrollo web para establecer tamaños de fuente o espaciado.

En este ejemplo, cada propiedad personalizada se determina mediante calc() , tomando el valor de la propiedad personalizada anterior y multiplicándolo por la proporción. Haciendo esto, podemos obtener el siguiente número en la escala.

Esto significa que las proporciones se calculan en tiempo de ejecución y puede cambiarlas actualizando solo el valor de la propiedad --font-scale . Por ejemplo:

 @media screen and (min-width: 800px) { :root { --font-scale: 1.33; } }

Esto es inteligente, conciso y mucho más rápido que calcular todos los valores nuevamente si desea cambiar la escala. También es algo que no haría en el código de producción.

Aunque el ejemplo anterior es útil para la creación de prototipos, en producción, preferiría ver algo como esto:

 :root { --font-size-1: 1.728rem; --font-size-2: 1.44rem; --font-size-3: 1.2em; --font-size-4: 1em; } @media screen and (min-width: 800px) { :root { --font-size-1: 2.369rem; --font-size-2: 1.777rem; --font-size-3: 1.333rem; --font-size-4: 1rem; } }

Similar al ejemplo en el artículo de Bill, encuentro útil ver cuáles son los valores reales. Leemos el código muchas más veces de las que lo escribimos y los valores globales, como las escalas de fuente, cambian con poca frecuencia en producción.

El ejemplo anterior todavía no es perfecto. Viola la regla anterior de que los valores globales deben ser estáticos . Preferiría usar variables de preprocesador y convertirlas en propiedades personalizadas dinámicas localmente usando las técnicas demostradas anteriormente.

También es importante evitar situaciones en las que pasemos de usar una propiedad personalizada a una propiedad personalizada diferente. Esto puede suceder cuando nombramos propiedades como esta.

Cambia el valor, no la variable

Cambiar el valor, no la variable, es una de las estrategias más importantes para usar las propiedades personalizadas de manera efectiva.

Como regla general, nunca debe cambiar qué propiedad personalizada se usa para un solo propósito. Es fácil de hacer porque así es exactamente como hacemos las cosas con los preprocesadores, pero tiene poco sentido con las propiedades personalizadas.

En este ejemplo, tenemos dos propiedades personalizadas que se utilizan en un componente de ejemplo. Cambio de usar el valor de --font-size-small a --font-size-large dependiendo del tamaño de la pantalla.

 :root { --font-size-small: 1.2em; --font-size-large: 2em; } .example { font-size: var(--font-size-small); } @media screen and (min-width: 800px) { .example { font-size: var(--font-size-large); } }

Una mejor manera de hacer esto sería definir una única propiedad personalizada en el ámbito del componente. Luego, usando una consulta de medios, o cualquier otro selector, cambie su valor.

 .example { --example-font-size: 1.2em; } @media screen and (min-width: 800px) { .example { --example-font-size: 2em; } }

Finalmente, en un solo lugar, uso el valor de esta propiedad personalizada:

 .example { font-size: var(--example-font-size); }

En este ejemplo y otros anteriores, las consultas de medios solo se han utilizado para cambiar el valor de las propiedades personalizadas. También puede notar que solo hay un lugar donde se usa la instrucción var() y se actualizan las propiedades CSS normales.

Esta separación entre declaraciones de variables y declaraciones de propiedades es intencional. Hay muchas razones para esto, pero los beneficios son más obvios cuando se piensa en un diseño receptivo.

Diseño receptivo con propiedades personalizadas

Una de las dificultades del diseño receptivo cuando se basa en gran medida en las consultas de medios es que, independientemente de cómo organice su CSS, los estilos relacionados con un componente en particular se fragmentan en la hoja de estilos.

Puede ser muy difícil saber qué propiedades de CSS van a cambiar. Aún así, las propiedades personalizadas de CSS pueden ayudarnos a organizar parte de la lógica relacionada con el diseño receptivo y hacer que trabajar con consultas de medios sea mucho más fácil.

Si cambia, es una variable

Las propiedades que cambian mediante consultas de medios son inherentemente dinámicas y las propiedades personalizadas proporcionan los medios para expresar valores dinámicos en CSS. Esto significa que si está utilizando una consulta de medios para cambiar cualquier propiedad CSS, debe colocar este valor en una propiedad personalizada.

Luego puede mover esto, junto con todas las reglas de medios, estados de desplazamiento o cualquier selector dinámico que defina cómo cambia el valor, a la parte superior del documento.

Separar la lógica del diseño

Cuando se hace correctamente, la separación de la lógica y el diseño significa que las consultas de medios solo se usan para cambiar el valor de las propiedades personalizadas . Significa que toda la lógica relacionada con el diseño receptivo debe estar en la parte superior del documento, y dondequiera que veamos una instrucción var() en nuestro CSS, sabemos de inmediato que esta propiedad cambia. Con los métodos tradicionales de escritura de CSS, no había forma de saberlo de un vistazo.

Muchos de nosotros nos volvimos muy buenos leyendo e interpretando CSS de un vistazo mientras rastreamos en nuestra cabeza qué propiedades cambiaron en diferentes situaciones. ¡Estoy cansado de esto, y no quiero hacer esto más! Las propiedades personalizadas ahora proporcionan un vínculo entre la lógica y su implementación, por lo que no necesitamos realizar un seguimiento de esto, ¡y eso es increíblemente útil!

El pliegue lógico

La idea de declarar variables en la parte superior de un documento o función no es una idea nueva. Es algo que hacemos en la mayoría de los idiomas, y ahora también es algo que podemos hacer en CSS. Escribir CSS de esta manera crea una distinción visual clara entre el CSS en la parte superior del documento y debajo. Necesito una manera de diferenciar estas secciones cuando hablo de ellas y la idea de un “pliegue lógico” es una metáfora que he comenzado a usar.
Sobre el pliegue contiene todas las variables del preprocesador y las propiedades personalizadas. Esto incluye todos los diferentes valores que puede tener una propiedad personalizada. Debería ser fácil rastrear cómo cambia una propiedad personalizada.

CSS debajo del pliegue es sencillo, altamente declarativo y fácil de leer. Se siente como CSS antes de las consultas de los medios y otras complejidades necesarias del CSS moderno.

Eche un vistazo a un ejemplo realmente simple de un sistema de cuadrícula flexbox de seis columnas:

 .row { --row-display: block; } @media screen and (min-width: 600px) { .row { --row-display: flex; } }

La propiedad personalizada --row-display se establece inicialmente en block . Por encima de 600 px, el modo de visualización se establece en flex.

Debajo del pliegue podría verse así:

 .row { display: var(--row-display); flex-direction: row; flex-wrap: nowrap; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6 { flex-grow: 0; flex-shrink: 0; } .col-1 { flex-basis: 16.66%; } .col-2 { flex-basis: 33.33%; } .col-3 { flex-basis: 50%; } .col-4 { flex-basis: 66.66%; } .col-5 { flex-basis: 83.33%; } .col-6 { flex-basis: 100%; }

Inmediatamente sabemos que --row-display es un valor que cambia. Inicialmente, será block , por lo que se ignorarán los valores flexibles.

Este ejemplo es bastante simple, pero si lo expandimos para incluir una columna de ancho flexible que llene el espacio restante, es probable que los valores flex-grow , flex-shrink y flex-basis deban convertirse en propiedades personalizadas. Puede probar esto o echar un vistazo a un ejemplo más detallado aquí.

Propiedades personalizadas para temas

En su mayoría, he argumentado en contra del uso de propiedades personalizadas para variables dinámicas globales y, con suerte, insinué que adjuntar propiedades personalizadas al selector :root se considera dañino en muchos casos. Pero cada regla tiene una excepción, y para las propiedades personalizadas, es la tematización.

El uso limitado de propiedades personalizadas globales puede facilitar mucho la creación de temas.

La temática generalmente se refiere a permitir que los usuarios personalicen la interfaz de usuario de alguna manera. Esto podría ser algo así como cambiar los colores en una página de perfil. O podría ser algo más localizado. Por ejemplo, puede elegir el color de una nota en la aplicación Google Keep.

Aplicación Google Keep

La creación de temas generalmente implica compilar una hoja de estilo separada para anular un valor predeterminado con las preferencias del usuario, o compilar una hoja de estilo diferente para cada usuario. Ambos pueden ser difíciles y tener un impacto en el rendimiento.

Con propiedades personalizadas, no necesitamos compilar una hoja de estilo diferente; solo necesitamos actualizar el valor de las propiedades de acuerdo a las preferencias del usuario. Dado que son valores heredados, si hacemos esto en el elemento raíz, pueden usarse en cualquier parte de nuestra aplicación.

Capitalizar propiedades dinámicas globales

Las propiedades personalizadas distinguen entre mayúsculas y minúsculas y dado que la mayoría de las propiedades personalizadas serán locales, si está utilizando propiedades dinámicas globales, puede tener sentido ponerlas en mayúsculas.

 :root { --THEME-COLOR: var(--user-theme-color, #d33a2c); }

La capitalización de variables a menudo significa constantes globales. Para nosotros, esto significará que la propiedad está configurada en otra parte de la aplicación y que probablemente no deberíamos cambiarla localmente.

Evite establecer directamente propiedades dinámicas globales

Las propiedades personalizadas aceptan un valor alternativo. Puede ser útil para evitar sobrescribir directamente el valor de una propiedad personalizada global y mantener los valores de usuario separados. Podemos usar el valor de reserva para hacer esto.

El ejemplo anterior establece el valor de --THEME-COLOR en el valor de --user-theme-color , si existe. Si no se establece --user-theme-color , se utilizará el valor de #d33a2c . De esta forma, no necesitamos proporcionar un respaldo cada vez que usamos --THEME-COLOR .

En el siguiente ejemplo, puede esperar que el fondo se establezca en green . Sin embargo, el valor de --user-theme-color no se ha establecido en el elemento raíz, por lo que el valor de --THEME-COLOR no ha cambiado.

 :root { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }

La configuración indirecta de propiedades dinámicas globales como esta evita que se sobrescriban localmente y garantiza que la configuración del usuario siempre se herede del elemento raíz. Esta es una convención útil para salvaguardar los valores de su tema y evitar la herencia no deseada.

Si queremos exponer propiedades específicas a la herencia, podemos reemplazar el selector :root con un selector * :

 * { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }

Ahora el valor de --THEME-COLOR se vuelve a calcular para cada elemento y, por lo tanto, se puede usar el valor local de --user-theme-color . En otras palabras, el color de fondo en este ejemplo será green .

Puede ver algunos ejemplos más detallados de este patrón en la sección sobre Manipulación de color con propiedades personalizadas.

Actualización de propiedades personalizadas con JavaScript

Si desea establecer propiedades personalizadas usando JavaScript, hay una API bastante simple y se ve así:

 const elm = document.documentElement; elm.style.setProperty('--USER-THEME-COLOR', 'tomato');

Aquí estoy configurando el valor de --USER-THEME-COLOR en el elemento del documento, o en otras palabras, el elemento :root donde será heredado por todos los elementos.

Esta no es una nueva API; es el mismo método de JavaScript para actualizar estilos en un elemento. Estos son estilos en línea, por lo que tendrán una mayor especificidad que el CSS normal.

Esto significa que es fácil aplicar personalizaciones locales:

 .note { --note-color: #eaeaea; } .note { background: var(--note-color); }

Aquí configuro un valor predeterminado para --note-color y alcanzo esto para el componente .note . Mantengo la declaración de variables separada de la declaración de propiedades, incluso en este ejemplo simple.

 const elm = document.querySelector('#note-uid'); elm.style.setProperty('--note-color', 'yellow');

Luego me dirijo a una instancia específica de un elemento .note y cambio el valor de la propiedad personalizada --note-color solo para ese elemento. Esto ahora tendrá una mayor especificidad que el valor predeterminado.

Puedes ver cómo funciona esto con este ejemplo usando React. Estas preferencias de usuario podrían guardarse en el almacenamiento local o, quizás en el caso de una aplicación más grande, en una base de datos.

Manipulación del color con propiedades personalizadas

Además de los valores hexadecimales y los colores con nombre, CSS tiene funciones de colores como rgb() y hsl() . Estos nos permiten especificar los componentes individuales de un color, como el matiz o la luminosidad. Las propiedades personalizadas se pueden utilizar junto con las funciones de color.

 :root { --hue: 25; } body { background: hsl(var(--hue), 80%, 50%); }

Esto es útil, pero algunas de las características más utilizadas de los preprocesadores son funciones de color avanzadas que nos permiten manipular el color usando funciones como aclarar, oscurecer o desaturar:

 darken($base-color, 10%); lighten($base-color, 10%); desaturate($base-color, 20%);

Sería útil tener algunas de estas características en los navegadores. Están llegando, pero hasta que tengamos funciones de modificación de color nativas en CSS, las propiedades personalizadas podrían llenar parte de ese vacío.

Hemos visto que las propiedades personalizadas se pueden usar dentro de funciones de color existentes como rgb() y hsl() pero también se pueden usar en calc() . Esto significa que podemos convertir un número real en un porcentaje multiplicándolo, por ejemplo, calc(50 * 1%) = 50% .

 :root { --lightness: 50; } body { background: hsl(25, 80%, calc(var(--lightness) * 1%)); }

La razón por la que queremos almacenar el valor de la luminosidad como un número real es para poder manipularlo con calc antes de convertirlo en un porcentaje. Por ejemplo, si quiero oscurecer un color un 20% , puedo multiplicar su luminosidad por 0.8 . Podemos hacer que esto sea un poco más fácil de leer separando el cálculo de la luminosidad en una propiedad personalizada de alcance local:

 :root { --lightness: 50; } body { --lightness: calc(var(--lightness * 0.8)); background: hsl(25, 80%, calc(var(--lightness) * 1%)); }

Incluso podríamos abstraer más de los cálculos y crear algo así como funciones de modificación de color en CSS usando propiedades personalizadas. Es probable que este ejemplo sea demasiado complejo para la mayoría de los casos prácticos de temas, pero demuestra todo el poder de las propiedades personalizadas dinámicas.

Simplificar temas

Una de las ventajas de usar propiedades personalizadas es la capacidad de simplificar la creación de temas. La aplicación no necesita saber cómo se usan las propiedades personalizadas. En su lugar, usamos JavaScript o código del lado del servidor para establecer el valor de las propiedades personalizadas. La forma en que se utilizan estos valores está determinada por las hojas de estilo.

Esto significa una vez más que podemos separar la lógica del diseño. Si tiene un equipo de diseño técnico, los autores pueden actualizar las hojas de estilo y decidir cómo aplicar propiedades personalizadas sin cambiar una sola línea de JavaScript o código de back-end.

Las propiedades personalizadas también permiten trasladar parte de la complejidad de la creación de temas al CSS y esta complejidad puede tener un impacto negativo en la capacidad de mantenimiento de su CSS, así que recuerde mantenerlo simple siempre que sea posible.

Uso de propiedades personalizadas hoy

Incluso si admite IE10 y 11, puede comenzar a usar propiedades personalizadas hoy. La mayoría de los ejemplos de este artículo tienen que ver con cómo escribimos y estructuramos CSS. Los beneficios son significativos en términos de mantenibilidad, sin embargo, la mayoría de los ejemplos solo reducen lo que de otro modo se podría hacer con un código más complejo.

Uso una herramienta llamada postcss-css-variables para convertir la mayoría de las características de las propiedades personalizadas en una representación estática del mismo código. Otras herramientas similares ignoran las propiedades personalizadas dentro de las consultas de medios o los selectores complejos, tratando las propiedades personalizadas como si fueran variables de preprocesador.

Lo que estas herramientas no pueden hacer es emular las funciones de tiempo de ejecución de las propiedades personalizadas. Esto significa que no hay funciones dinámicas como la creación de temas o el cambio de propiedades con JavaScript. Esto podría estar bien en muchas situaciones. Dependiendo de la situación, la personalización de la interfaz de usuario podría considerarse una mejora progresiva y el tema predeterminado podría ser perfectamente aceptable para los navegadores más antiguos.

Cargando la hoja de estilo correcta

Hay muchas maneras de usar postCSS. Utilizo un proceso de gulp para compilar hojas de estilo separadas para navegadores más nuevos y más antiguos. Una versión simplificada de mi tarea de gulp se ve así:

 import gulp from "gulp"; import sass from "gulp-sass"; import postcss from "gulp-postcss"; import rename from "gulp-rename"; import cssvariables from "postcss-css-variables"; import autoprefixer from "autoprefixer"; import cssnano from "cssnano"; gulp.task("css-no-vars", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssvariables(), cssnano()])) .pipe(rename({ extname: ".no-vars.css" })) .pipe(gulp.dest("./dist/css")) ); gulp.task("css", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssnano()])) .pipe(rename({ extname: ".css" })) .pipe(gulp.dest("./dist/css")) );

Esto da como resultado dos archivos CSS: uno normal con propiedades personalizadas ( styles.css ) y otro para navegadores más antiguos ( styles.no-vars.css ). Quiero que IE10 y 11 se sirvan styles.no-vars.css y otros navegadores para obtener el archivo CSS normal.

Normalmente, recomendaría el uso de consultas de funciones, pero IE11 no admite consultas de funciones y hemos usado propiedades personalizadas de manera tan extensa que servir una hoja de estilo diferente tiene sentido en este caso.

Servir de forma inteligente una hoja de estilo diferente y evitar un destello de contenido sin estilo no es una tarea sencilla. Si no necesita las características dinámicas de las propiedades personalizadas, podría considerar servir todos los estilos de navegador.no styles.no-vars.css y usar propiedades personalizadas simplemente como una herramienta de desarrollo.

Si desea aprovechar al máximo todas las características dinámicas de las propiedades personalizadas, le sugiero que utilice una técnica CSS crítica. Siguiendo estas técnicas, la hoja de estilo principal se carga de forma asíncrona mientras que el CSS crítico se representa en línea. El encabezado de su página podría verse así:

 <head> <style> /* inlined critical CSS */ </style> <script> loadCSS('non-critical.css'); </script> </head>

Podemos extender esto para cargar styles.css o styles.no-vars.css dependiendo de si el navegador admite propiedades personalizadas. Podemos detectar soporte como este:

 if ( window.CSS && CSS.supports('color', 'var(--test)') ) { loadCSS('styles.css'); } else { loadCSS('styles.no-vars.css'); }

Conclusión

Si ha tenido problemas para organizar CSS de manera eficiente, tiene dificultades con los componentes receptivos, desea implementar temas del lado del cliente o simplemente quiere comenzar con el pie derecho con propiedades personalizadas, esta guía debe decirle todo lo que necesita saber.

Todo se reduce a comprender la diferencia entre variables dinámicas y estáticas en CSS, así como algunas reglas simples:

  1. Separar la lógica del diseño;
  2. Si cambia una propiedad CSS, considere usar una propiedad personalizada;
  3. Cambie el valor de las propiedades personalizadas, no qué propiedad personalizada se utiliza;
  4. Las variables globales suelen ser estáticas.

If you follow these conventions, you will find that working with custom properties is a whole lot easier than you think. This might even change how you approach CSS in general.

Otras lecturas

  • “It's Time To Start Using Custom Properties,” Serg Hospodarets
    A general introduction to the syntax and the features of custom properties.
  • “Pragmatic, Practical, And Progressive Theming With Custom Properties,” Harry Roberts
    More useful information on theming.
  • Custom Properties Collection, Mike Riethmuller on CodePen
    A number of different examples you can experiment with.