Atributos de presentación frente a CSS
Por qué un fill escrito en el elemento pierde contra cualquier regla de CSS, dónde encaja el atributo en la cascada, y la única forma fiable de blindar un color.
Dónde entra el atributo en la cascada
Un atributo de presentación se traduce a una declaración de CSS y se inserta como si fuera una regla de autor situada al principio de la primera hoja de estilos de autor, con especificidad cero. No es un valor por defecto del elemento, no es una regla del navegador y no es un estilo en línea. Es una regla de autor debilísima.
De ahí sale el orden completo, de menos a más fuerte:
- Declaraciones del navegador.
- Declaraciones del usuario.
- Atributos de presentación.
- Cualquier regla de autor:
svg,.icono,#logo, todas. styleen línea.- Todo lo anterior marcado con
!important, en el orden inverso.
El resultado incómodo:
<style> * { fill: rebeccapurple; }</style><svg viewBox="0 0 24 24" width="48"> <circle cx="12" cy="12" r="10" fill="#a6e3a1" /></svg>El círculo sale morado. El selector universal tiene especificidad cero igual que el atributo, pero viene después en el orden de aparición, y a igualdad de especificidad y origen manda el último. No hay selector lo bastante débil para perder contra un atributo de presentación: incluso el más débil de todos empata y gana por orden.
Un style en línea sí gana al atributo, porque los estilos en línea van en su propio escalón:
<circle fill="#a6e3a1" style="fill: #f38ba8" r="10" cx="12" cy="12" />Sale rojo. Y por si queda duda: el atributo de presentación no admite !important. La sintaxis fill="red !important" no es un valor de color válido y se descarta entero, dejando el elemento con el valor heredado.
Por qué se diseñó así
La colocación parece hostil hasta que se mira el problema que resolvía. SVG 1.0 se publicó en 2001, y su ambición era ser un formato de documento que se pudiera estilar con CSS igual que HTML. Pero los ficheros SVG los generaban herramientas de dibujo, que escriben la apariencia en el propio elemento porque no tienen un concepto de hoja de estilos.
Si los atributos hubieran tenido la fuerza de un estilo en línea, un fichero exportado de Illustrator habría sido inestilables: cada fill en cada ruta habría bloqueado cualquier intento de recolorearlo desde el documento anfitrión. Toda la práctica moderna de los iconos SVG (un fichero neutro que hereda el color del contexto) sería imposible.
Colocándolos al fondo de la cascada, la especificación consiguió las dos cosas a la vez: el fichero se ve correctamente por sí solo, y el documento que lo incrusta puede reescribirlo entero sin tocar el marcado. Es una decisión de diseño acertada cuyo coste es exactamente el que estás pagando cuando un selector suelto te cambia un icono.
Qué se puede escribir como atributo y qué no
No todo atributo de un elemento SVG es un atributo de presentación. La distinción es exacta: es atributo de presentación si existe una propiedad CSS con ese nombre. fill, stroke, opacity, clip-path, filter, text-anchor lo son. viewBox, points, d, preserveAspectRatio, gradientUnits, x1 de un gradiente y refX de un marcador no lo son: son atributos geométricos o de configuración, no participan en la cascada, y no se pueden escribir desde CSS.
Esa frontera es la fuente de una frustración recurrente. No puedes cambiar el viewBox desde una media query, ni el gradientUnits desde un tema, ni los points de un polígono desde un estado de hover. Para todo eso hay que tocar el atributo desde JavaScript o generar marcado distinto.
Los atributos de presentación se pueden escribir de las tres formas, y las tres producen la misma propiedad computada:
<circle fill="#89b4fa" /><circle style="fill: #89b4fa" /><circle class="azul" /> <!-- con .azul { fill: #89b4fa } -->La lista completa de propiedades disponibles se trata en qué propiedades SVG viven en CSS.
Blindar un valor de verdad
Cuando un color no debe poder sobrescribirse (la marca en un logotipo, el rojo de un icono de error), hay tres niveles de defensa y conviene elegir el mínimo que funcione.
Nivel uno: style en línea. Gana a cualquier regla de autor sin !important. Es la opción sensata para un logotipo incrustado en un contexto que no controlas.
<path style="fill:#e0234e" d="..." />Nivel dos: style en línea con !important. Gana a todo salvo a un !important de usuario. Reservado para cuando el contexto es hostil de verdad, como un widget que se inyecta en páginas de terceros.
Nivel tres: un shadow DOM. El estilo del documento no cruza la frontera del shadow salvo por herencia y por custom properties. Es la única barrera real, y es la razón por la que los componentes web de terceros no sufren este problema.
Y hay una defensa que no funciona y que todo el mundo prueba: subir la especificidad del selector propio. #logo path { fill: #e0234e } gana hoy, y pierde mañana contra #app #logo path. La escalada de especificidad no es una estrategia, es un aplazamiento.
Merece la pena decir en qué dirección va la práctica actual: en un sistema de diseño, lo correcto es no fijar el color en el SVG en absoluto. El icono declara fill="currentColor" y el color lo pone el contenedor con la propiedad color. Así el icono nunca pelea con la cascada: participa en ella por la vía de la herencia, que es la que estaba pensada para esto.