Saltar al contenido

Custom properties dentro de SVG

Las variables CSS como único canal que atraviesa el árbol de sombra de use, el patrón de icono multicolor tematizable, y la trampa de los valores de reserva.

Se comportan igual que en HTML

No hay nada especial en cómo funcionan dentro de un SVG: se declaran en cualquier elemento, se heredan a los descendientes y se consumen con var(). Lo distinto es lo que permiten hacer.

HTML
<svg viewBox="0 0 24 24" width="48" height="48" class="icono-doc">  <rect x="4" y="2" width="14" height="20" rx="2"        fill="var(--relleno, #45475a)" />  <path d="M8 8h6M8 12h6M8 16h4"        stroke="var(--lineas, #cdd6f4)" stroke-width="1.5" stroke-linecap="round" /></svg>
CSS
.icono-doc { --relleno: #1e1e2e; --lineas: #89b4fa; }[data-tema="claro"] .icono-doc { --relleno: #eff1f5; --lineas: #1e66f5; }

Dos observaciones que importan.

La primera: el valor de reserva del segundo argumento de var() no es un detalle cosmético. Hace que el fichero se vea correctamente abierto directamente en el navegador, incrustado en un correo, o en cualquier contexto donde el CSS del sistema de diseño no llegue. Un icono cuyo fill es var(--relleno) sin reserva se pinta con el valor inicial de fill, que es negro, y en un fondo oscuro desaparece.

La segunda: las custom properties se pueden usar en cualquier atributo de presentación, porque son propiedades. No se pueden usar en viewBox, en d ni en points, porque esos no lo son.

Por qué cruzan el árbol de sombra

Este es el punto que justifica el nivel entero. Cuando use instancia un symbol, el contenido clonado vive en un árbol de sombra. Los selectores del documento no llegan ahí: #sprite .fondo { fill: red } no encuentra nada, porque .fondo está dentro de la sombra y el selector se evalúa en el documento ligero.

Lo que sí cruza es la herencia. El árbol de sombra hereda del elemento use, exactamente igual que un elemento HTML hereda de su padre. Y como las custom properties son propiedades heredadas, cruzan.

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true">  <symbol id="i-carpeta" viewBox="0 0 24 24">    <path fill="var(--c-cuerpo, #585b70)" d="M2 6h7l2 2h11v12H2z" />    <path fill="var(--c-pestana, #7f849c)" d="M2 6h7l2 2H2z" />  </symbol></svg><svg class="ico ico-aviso" width="32" height="32" aria-hidden="true">  <use href="#i-carpeta" /></svg><svg class="ico ico-ok" width="32" height="32" aria-hidden="true">  <use href="#i-carpeta" /></svg>
CSS
.ico-aviso { --c-cuerpo: #f9e2af; --c-pestana: #fab387; }.ico-ok    { --c-cuerpo: #a6e3a1; --c-pestana: #94e2d5; }

Dos instancias del mismo símbolo, con paletas distintas, sin duplicar el marcado y sin un solo selector que apunte dentro de la sombra. Es el patrón, y no hay otro que funcione.

Registrar con @property

@property declara el tipo de una custom property, su valor inicial y si se hereda. Dos beneficios concretos en SVG.

Uno: el valor inválido no rompe nada. Con syntax: "<color>", cualquier valor que no sea un color se descarta en el parseo y la propiedad conserva su initial-value.

Dos: la variable se puede animar e interpolar. Una custom property sin registrar es una cadena de texto: el navegador no sabe que #89b4fa y #f38ba8 son colores, así que una transición sobre ella salta de golpe a la mitad. Registrada como color, se interpola.

CSS
@property --ico-cuerpo {  syntax: "<color>";  inherits: true;  initial-value: #585b70;}.ico { transition: --ico-cuerpo 200ms ease; }.ico:hover { --ico-cuerpo: #89b4fa; }

Ese transition sobre una variable es lo que permite animar el color de un icono dentro de un árbol de sombra, donde no puedes poner una transición en el elemento que realmente se pinta porque no puedes seleccionarlo. La transición vive en el use, la variable viaja por herencia, y el fill de dentro se recalcula en cada fotograma.

Un syntax de tipo <length> o <number> sirve igual para animar cosas que no son colores: el stroke-width de un icono de línea, la stroke-dashoffset de un trazo progresivo, o la opacidad de una capa.

Los límites

Tres cosas que las custom properties no arreglan.

No cruzan la frontera del documento. Un SVG cargado con img o como background-image no ve las variables de la página, por la misma razón que no ve color. Solo funcionan con SVG en línea y con use.

No se pueden usar en atributos que no son propiedades. viewBox="0 0 var(--w) 24" no funciona. Nada geométrico de configuración funciona.

No sustituyen a una API de componente. Un icono con siete variables es un icono que nadie sabe usar. Dos o tres tonos con nombres semánticos (--ico-base, --ico-acento) es un contrato manejable; siete es un mecano.

Y un apunte de nomenclatura que ahorra mucho: nombra por papel, no por color. --ico-acento sobrevive a un rediseño; --ico-azul obliga a un renombrado global el día que el acento pase a ser verde.