Históricamente, uno de los principales problemas de CSS para un programador, es que CSS es de naturaleza global. Esto significa que en todo momento debemos tener en cuenta los estilos de todo el documento, cosa que en aplicaciones grandes es muy complejo. Cuando desarrollamos ejemplos pequeños en CSS, no suelen haber demasiados problemas. Sin embargo, cuando el código CSS o el proyecto crece, comienzan los problemas.
Pero eso ha cambiado con la regla @scope
.
La regla @scope
La regla @scope
nos permite aplicar estilos a un ámbito limitado, es decir, reducir el HTML al que se le aplican los estilos. Esto es una herramienta genial, muy interesante para evitar conflictos CSS y simplificar nuestro código.
La sintaxis puede ser cualquiera de las siguientes:
Regla | Descripción |
---|---|
@scope | Limita el alcance al contenedor donde se declara. Usado en un <style> del HTML. |
@scope (start) | Limita el alcance inicial a partir del cuál se aplican los estilos. |
@scope (start) to (end) | Delimita el alcance inicial y final del cuál se aplican los estilos. |
Mediante estas tres modalidades de uso de @scope
podemos limitar el alcance de prácticamente todas las formas. Veamos cada una de ellas en detalle.
La regla @scope
en línea
La forma más simple de aplicar la regla @scope
es escribirla directamente en el HTML, definiéndola mediante una etiqueta <style>
. Observa el siguiente ejemplo. La regla @scope
definida en una etiqueta <style>
actúa sólo en el ámbito de su etiqueta padre, o sea, el <div class="container">
, por lo que los estilos están limitados sólo a la etiqueta <strong>
interior:
<p>Hola, soy <strong>ManzDev</strong> exterior.</p>
<div class="container">
<style>
@scope {
strong {
background: orangered;
color: white;
padding: 0.2rem;
}
}
</style>
<p>Hola, soy el <strong>ManzDev</strong> interior.</p>
</div>
Repasemos varios detalles clave aquí:
- El primer
<strong>
está fuera de.container
. - El segundo
<strong>
está dentro de.container
. - El
@scope
se declara en un<style>
dentro de.container
, por lo que sólo le afecta a él.
Esto es la funcionalidad básica de @scope
. Sin embargo, no es obligatorio utilizarlo en el HTML en una etiqueta <style>
, se puede utilizar en hojas de estilo CSS externas con otra sintaxis, como veremos a continuación.
La regla @scope
en CSS
La regla @scope
también se puede utilizar desde ficheros .css
externos, pero su sintaxis cambia un poco. Ahora delimitaremos el alcance de las etiquetas HTML a las que afectará, indicando un principio y un final en los parámetros de la regla @scope
. De esta forma, indicamos a que partes del documento queremos que afecten los estilos:
En esta imagen se puede ver que el alcance de los estilos comienza en la etiqueta .grandparent
(excluyéndola) hasta la etiqueta .child
(excluyéndola), por lo que afecta sólo a .parent
.
Límite inicial de @scope
Veamos un fragmento de código utilizando la regla @scope
sobre el código HTML de la imagen anterior. Entre paréntesis delimitaremos el ámbito inicial para el que se van a aplicar los estilos CSS. Atentos a los detalles:
- 1️⃣ El
@scope
comienza desde el interior de.grandparent
(este excluído) - 2️⃣ El CSS añade un borde verde a todos los
<div>
- 3️⃣ El HTML define 3 etiquetas
<div>
, una dentro de otra, con un texto cada una.
@scope (.grandparent) {
div {
border: 2px solid green;
padding: 3px;
}
}
<div class="grandparent">
Grandparent
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>
</div>
Observa que en la demo, los estilos se están aplicando a los elementos .parent
y .child
. Esto ocurre porque le hemos dicho a la regla @scope
que comience a aplicar estilos a todos los <div>
a partir del elemento .grandparent
(excluído).
Ten en cuenta que en estos ejemplos estamos seleccionando elementos genéricos
div
para que sea más sencillo el ejemplo, pero se puede complicar con cualquier otro selector, haciéndolo más potente.
Límite final con to
Ahora, vamos a añadir la palabra clave to
y un segundo parámetro a la regla @scope
, que nos permitirá no sólo indicar el límite inicial, sino también el límite final. Nuevamente, atentos a los detalles:
- 1️⃣ El
@scope
comienza desde el interior de.grandparent
(este excluído) - 2️⃣ El
@scope
termina en el elemento.child
(este excluído)
@scope (.grandparent) to (.child) {
div {
border: 2px solid green;
padding: 3px;
}
}
<div class="grandparent">
Grandparent
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>
</div>
Observa que sólo se está aplicando el borde al elemento .parent
. A .child
puede parecer que se le aplica, pero observa que no tiene borde como en el ejemplo anterior, simplemente aparece dentro porque son elementos anidados.
Por ejemplo, si modificamos el límite final e indicamos .child > *
(todo lo que incluya el .child
), entonces si que detectará el último elemento .child
:
@scope (.grandparent) to (.child > *) {
div {
border: 2px solid green;
padding: 3px;
}
}
<div class="grandparent">
Grandparent
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>
</div>
O observa el siguiente ejemplo, donde aplicamos los límites desde body
hasta .child
(este excluído). Te encontrarás con que los dos primeros elementos .grandparent
y .parent
tienen borde, mientras que .child
no lo tiene:
@scope (body) to (.child) {
div {
border: 2px solid green;
padding: 3px;
}
}
<div class="grandparent">
Grandparent
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>
</div>
Límite final inexistente
Podría darse la circunstancia de que definamos un límite de ámbito final que realmente no existe en el HTML, como por ejemplo la etiqueta .grandson
:
@scope (.grandparent) to (.grandson) {
div {
border: 2px solid green;
padding: 3px;
}
}
<div class="grandparent">
Grandparent
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>
</div>
Como el elemento con clase .grandson
no existe, el navegador interpretará la regla scope como si no tuviera límite final, es decir, como @scope (.grandparent)
. De esta forma, estaría seleccionando todos los <div>
que se encuentren en el interior de un .grandparent
(este excluído).
Múltiples límites
Otro detalle interesante sobre la regla @scope
es que podemos utilizar una lista de múltiples valores, de forma similar al combinador lógico :is()
o :where()
. Observa el siguiente ejemplo:
@scope (.post-container, .comments-container) to (.items) {
.element {
background: indigo;
color: white;
}
}
<div class="post-container">
<div class="element">Element 1 (Selected)</div>
<div class="items">
<div class="element">Element 2 (Not selected)</div>
<div class="element">Element 3 (Not selected)</div>
</div>
</div>
<div class="element">Element 1 (Not Selected)</div>
<div class="comments-container">
<div class="element">Element 1 (Selected)</div>
<div class="items">
<div class="element">Element 2 (Not selected)</div>
<div class="element">Element 3 (Not selected)</div>
</div>
</div>
En este ejemplo, dentro de los selectores de los paréntesis indicamos múltiples valores, por lo que podemos establecer selecciones múltiples y no atarnos a elecciones más sencillas. Esto puede resultar especialmente útil de cara a la mantenibilidad.
La pseudoclase :scope
La pseudoclase :scope
se puede utilizar en las reglas @scope
para hacer referencia al ámbito inicial del scope. Por ejemplo, en el siguiente fragmento de código, :scope
es una referencia a .grandparent
:
@scope (.grandparent) {
:scope > div {
border: 2px solid orangered;
padding: 5px;
margin: 10px;
}
}
<div class="grandparent">
Grandparent
<div class="parent">
Parent
<div class="child">
Child
</div>
</div>
</div>
En este caso, el navegador aplicaría los estilos sólo a .parent
, ya que es el <div>
que está inmediatamente a continuación de .grandparent
, que es el :scope
. Esta característica podría ser realmente útil si utilizamos una lista de múltiples valores:
@scope (.grandparent, .container) {
:scope > .element {
border: 2px solid orangered;
padding: 5px;
margin: 10px;
}
}
Aquí, estaríamos seleccionando todos los elementos .element
que son hijos inmediatamente directos de .grandparent
o de .container
, que son los dos ámbitos de :scope
en este ejemplo.
También podríamos seleccionar elementos según sus ancestros:
@scope (.grandparent) to (.child) {
.dark :scope > .element {
background: #222;
color: white;
padding: 15px;
}
}
En este caso, seleccionamos los .element
que estén dentro de un .grandparent
que tenga algún padre con la clase .dark
. Podríamos también utilizar el carácter &
del Nesting CSS en lugar del :scope
, ya que funciona de una forma muy similar.
Reemplazo de BEM + Sass
Una característica muy interesante que brinda @scope
es la posibilidad de crear bloques de «componentes CSS» al estilo de como se suele hacer con Sass, pero de forma nativa. Observemos el siguiente código Sass, donde aprovechamos nomenclatura BEM para crear nombres de clase que eviten especificidades altas o nombres de clase potencialmente colisionables.
En Sass lo haríamos así:
.post-container {
&__title { font-size: 3rem; }
&__subtitle { font-size: 1.5rem; }
}
.content-container {
&__code { color: #848848; }
}
<div class="post-container">
<h3 class="post-container__title">Título</h3>
<p class="post-container__subtitle">Subtítulo</p>
</div>
<div class="content-container">
<p>Utilizando la regla <code class="content-container__code">@scope</code> en CSS.</p>
</div>
Esto, al pasarlo por un procesador de Sass, permitiría producir un código CSS donde los nombres de clases anidados que empiecen por __
, se concatenan (atención al nombre de clases). De esta forma el selector tiene una especificidad mínima, aunque sea más complejo:
.post-container__title { font-size: 3rem; }
.post-container__subtitle { font-size: 1.5rem; }
.content-container__code { color: #848848; }
Durante mucho tiempo, esto ha sido una de las formas de evitar aumentar la especificidad de CSS en proyectos que utilizan Sass, a la vez que facilitan una forma predecible de nombrar elementos.
Ahora, con @scope
, en su lugar podríamos aprovechar la regla @scope
(incluso usando los límites superiores si queremos excluir alguna parte) para obtener un código nativo muy fácil de leer, modificar, mantener y delimitar:
@scope (.post-container) {
.title { font-size: 3rem; }
.subtitle { font-size: 1.5rem; }
}
@scope (.content-container) to (.items) {
.code { color: #848848; }
}
<div class="post-container">
<h3 class="title">Título</h3>
<p class="subtitle">Subtítulo</p>
</div>
<div class="content-container">
<p>Utilizando la regla <code class="code">@scope</code> en CSS.</p>
</div>