Importar dentro de una capa: domesticar el CSS ajeno
La sintaxis completa de @import con capa, cómo encerrar una hoja de terceros que no puedes editar, qué pasa con las capas que ese fichero declare por su cuenta y qué cuesta hacerlo así.
El problema que más veces resuelven las capas en un proyecto real no es organizar tu propio CSS: es someter el CSS de otro. Una biblioteca de componentes, un tema comprado, el editor visual del gestor de contenidos, el widget del proveedor de pagos. Todos traen selectores escritos con criterios que no son los tuyos, a veces con identificadores y con importancias, y ninguno se puede editar. @import con asignación de capa convierte ese problema estructural en una línea.
- Escribir la forma completa de
@importcon capa, condición de soporte y consulta de medios. - Encerrar una hoja de terceros en una capa sin modificar su contenido.
- Predecir dónde acaban las capas que declare por su cuenta la hoja importada.
- Valorar el coste de red de
@importfrente a las alternativas en construcción.
La sintaxis completa
La regla admite, en este orden, la dirección del recurso, la asignación de capa, una condición de soporte y una lista de consultas de medios. Todo salvo la dirección es opcional:
@import url("tema.css") layer(terceros) supports(display: grid) screen;
La asignación de capa tiene dos variantes. Con nombre, la hoja importada entra en esa capa concreta, que puedes reabrir después desde tu propio CSS:
@import url("normalize.css") layer(reset);
Sin nombre, con la palabra clave a secas, entra en una capa anónima que nadie puede referenciar desde fuera:
@import url("widget-proveedor.css") layer;
La segunda forma es la adecuada cuando lo único que quieres es que ese CSS quede por debajo de todo lo tuyo y no tienes ninguna intención de añadirle reglas después. Al no tener nombre, ni tú ni nadie puede abrirla accidentalmente y colar reglas ahí.
Sobre la posición de la regla en el fichero hay dos condiciones que producen fallos silenciosos si se incumplen. La primera: todas las reglas @import deben ir al principio de la hoja, antes de cualquier regla de estilo. La segunda, que es una concesión pensada precisamente para este caso: pueden ir precedidas por sentencias @layer, de modo que el patrón canónico es declarar el orden y después importar.
@layer reset, terceros, base, componentes, utilidades;
@import url("normalize.css") layer(reset);
@import url("biblioteca.css") layer(terceros);
@layer base {
:root { color-scheme: light dark; }
}
Si colocas una regla de estilo antes de un @import, ese @import se ignora por completo y sin aviso. Es uno de los errores más frecuentes al reorganizar una hoja.
El caso canónico
Supón una biblioteca cuyo botón se estila así:
/* biblioteca.css — no lo puedes tocar */
#app .btn.btn-primary { background: #0d6efd; }
Sin capas, sobrescribir eso exige igualar 1-2-0, y para eso tienes que conocer la estructura interna de una hoja ajena que además puede cambiar en la próxima versión menor. Es un acoplamiento pésimo: tu CSS depende de detalles de implementación de otro.
Con la importación encapada, el problema desaparece:
@layer terceros, propio;
@import url("biblioteca.css") layer(terceros);
@layer propio {
.btn-primary { background: oklch(58% 0.19 258); }
}
Gana lo tuyo con 0-1-0. Y lo que es más importante: seguirá ganando si en la siguiente versión la biblioteca cambia sus selectores, porque tu autoridad no depende de la forma de los suyos. Has sustituido un acoplamiento frágil por una declaración estable.
Queda una excepción que conviene tener presente: si la hoja de terceros usa !important, la inversión que ya conoces juega en tu contra. Sus declaraciones importantes, al estar en una capa más temprana, vencen a las tuyas importantes. Para esos casos concretos la salida es marcar tus reglas como importantes desde una capa aún más temprana, o tratar ese puñado de propiedades como excepciones documentadas.
Capas dentro de capas importadas
Una hoja importada puede traer sus propias reglas @layer. La especificación resuelve el solapamiento de forma limpia: las capas que declare la hoja importada se anidan dentro de la capa a la que la importaste.
Si biblioteca.css contiene @layer base { ... } y tú la importas con layer(terceros), esas reglas acaban en la capa terceros.base, que es una subcapa de terceros y no tiene nada que ver con una capa base que tú hayas declarado en el nivel superior. Son dos capas distintas con el mismo último segmento, y no se pisan.
Eso significa que puedes importar dos bibliotecas que internamente usan capas llamadas igual sin que colisionen, siempre que las importes a capas distintas. Es el mecanismo más parecido a un espacio de nombres que ofrece @layer, y aunque no es encapsulación de verdad, resuelve el caso práctico.
Aquí hay una decisión de ingeniería que casi nadie plantea en voz alta y que tiene consecuencias medibles en el tiempo de pintado. @import tiene un coste de red específico: el navegador no puede descubrir la hoja importada hasta que ha descargado y analizado la hoja que la importa. Con una etiqueta de enlace en el documento, el analizador especulativo la ve en el primer trozo de HTML y empieza la descarga de inmediato; con @import se serializa la cadena, y cada nivel de importación añade un viaje completo al camino crítico del renderizado, que además es bloqueante porque el CSS lo es. La tentación al adoptar capas es convertir todos los enlaces en importaciones para poder asignarles capa, y eso es cambiar un problema de arquitectura, que no cuesta milisegundos, por un problema de rendimiento que sí los cuesta y que se paga en cada visita. Las alternativas, en orden de preferencia. La primera y con diferencia la mejor: envolver el fichero de terceros en tiempo de construcción, generando un fichero que contenga @layer terceros { ... } alrededor del original; el resultado se sirve con una etiqueta de enlace normal, sin cadena de importaciones, y el efecto en la cascada es idéntico. Cualquier empaquetador o un simple script de dos líneas lo hace, y como el contenido ajeno no se modifica, actualizar la dependencia sigue siendo trivial. La segunda: si la hoja de terceros es pequeña y no crítica, importarla y asumir el viaje extra. La tercera, para el CSS que ya se cargaba con @import de todas formas: añadir la capa sale gratis, porque el coste ya estaba pagado. Lo que conviene evitar es la cuarta opción, que es la que se ve en muchos proyectos: mantener el enlace y además duplicar reglas para ganar por especificidad, que es pagar los dos costes a la vez.
Condiciones en la importación
Las dos partes opcionales que quedan tienen usos concretos y poco conocidos.
supports() importa la hoja solo si el motor entiende la declaración indicada. Sirve para servir hojas alternativas completas según la capacidad del navegador, sin duplicar reglas ni recurrir a JavaScript:
@import url("layout-moderno.css") layer(layout) supports(grid-template-rows: subgrid);
@import url("layout-clasico.css") layer(layout) supports(not (grid-template-rows: subgrid));
La lista de consultas de medios al final restringe la aplicación de la hoja importada, aunque conviene saber que no evita la descarga: el navegador se baja la hoja igualmente y solo la aplica si la consulta se cumple, con menor prioridad. Es útil para separar hojas de impresión, no para ahorrar bytes.
@import url("impresion.css") layer(impresion) print;
Con esto queda cubierta la asignación de capas desde fuera. Lo que falta es la estructura interna: capas dentro de capas, capas sin nombre y el orden implícito que aparece entre ellas, que es la lección siguiente.