Cómo comenzar a usar las consultas de estilo

La capacidad de consultar el tamaño intercalado de un elemento superior y los valores de unidad de consulta del contenedor recientemente alcanzó la compatibilidad estable en todos los motores de navegador modernos.

Navegadores compatibles

  • Chrome: 105.
  • Edge: 105.
  • Firefox: 110.
  • Safari: 16.

Origen

Sin embargo, la especificación de contención incluye más que solo consultas de tamaño; también permite consultar los valores de estilo de un elemento superior. A partir de Chromium 111, podrás aplicar el aislamiento de diseño para los valores de propiedades personalizadas y consultar un elemento superior para obtener el valor de una propiedad personalizada.

Navegadores compatibles

  • Chrome: 111
  • Edge: 111.
  • Firefox: No es compatible.
  • Safari: 18.

Origen

Esto significa que tenemos un control aún más lógico de los estilos en CSS y permite una mejor separación de la lógica y la capa de datos de una aplicación de sus estilos.

La especificación del nivel 3 del módulo de contención de CSS, que abarca las consultas de tamaño y estilo, permite consultar cualquier estilo desde un elemento superior, incluidos los pares de propiedad y valor, como font-weight: 800. Sin embargo, en el lanzamiento de esta función, las consultas de estilo actualmente solo funcionan con valores de propiedades personalizadas de CSS. Esto sigue siendo muy útil para combinar estilos y separar los datos del diseño. Veamos cómo usar las consultas de estilo con las propiedades personalizadas de CSS:

Cómo comenzar a usar las consultas de estilo

Supongamos que tenemos el siguiente código HTML:

<ul class="card-list">
  <li class="card-container">
    <div class="card">
      ...
    </div>
  </li>
</ul>

Para usar consultas de diseño, primero debes configurar un elemento de contenedor. Esto requiere un enfoque ligeramente diferente según si consultas un elemento superior directo o indirecto.

Consulta a los elementos superiores directos

Diagrama de una consulta de estilo.

A diferencia de las consultas de estilo, no necesitas aplicar contención mediante las propiedades container-type o container a .card-container para que .card pueda consultar los estilos de su elemento superior directo. Sin embargo, debemos aplicar los estilos (valores de propiedades personalizadas en este caso) a un contenedor (.card-container en este caso) o a cualquier elemento que contenga el elemento al que le aplicaremos estilos en el DOM. No podemos aplicar los estilos que consultamos en el elemento directo al que le aplicamos estilos con esa consulta, ya que esto podría causar bucles infinitos.

Para consultar directamente a un elemento superior, puedes escribir lo siguiente:

/* styling .card based on the value of --theme on .card-container */
@container style(--theme: warm) {
  .card {
    background-color: wheat;
    border-color: brown; 
    ...
  }
}

Quizás hayas notado que la consulta de estilo une la consulta con style(). Esto permite desambiguar los valores de tamaño de los estilos. Por ejemplo, puedes escribir una consulta para el ancho del contenedor como @container (min-width: 200px) { … }. Esto aplicaría estilos si el contenedor superior tuviera al menos 200 px de ancho. Sin embargo, min-width también puede ser una propiedad CSS, y puedes consultar el valor CSS de min-width con consultas de estilo. Por eso, usarías el wrapper style() para que la diferencia sea clara: @container style(min-width: 200px) { … }.

Aplica diseño a elementos superiores no directos

Si quieres consultar los estilos de cualquier elemento que no sea un elemento superior directo, debes asignarle un container-name. Por ejemplo, podemos aplicar estilos a .card según los estilos de .card-list si otorgamos a .card-list un container-name y hacemos referencia a él en la consulta de estilo.

/* styling .card based on the value of --moreGlobalVar on .card-list */
@container cards style(--moreGlobalVar: value) {
  .card {
    ...
  }
}

Por lo general, se recomienda asignar nombres a tus contenedores para dejar en claro lo que estás consultando y desbloquear la capacidad de acceder a esos contenedores con mayor facilidad. Un ejemplo de cuándo esto resulta útil es si quieres aplicar diseño a elementos dentro de .card directamente. Sin un contenedor con nombre en .card-container, no pueden consultarlo directamente.

Pero todo esto tiene mucho más sentido en la práctica. Veamos algunos ejemplos:

Consultas de diseño en acción

Imagen de demostración con varias tarjetas de productos, algunas con etiquetas de &quot;nuevo&quot; o &quot;stock bajo&quot;, y la tarjeta de &quot;stock bajo&quot; con un fondo rojo.

Las consultas de estilo son particularmente útiles cuando tienes un componente reutilizable con varias variaciones o cuando no tienes control sobre todos tus estilos, pero necesitas aplicar cambios en ciertos casos. En este ejemplo, se muestra un conjunto de tarjetas de productos que comparten el mismo componente de tarjeta. Algunas tarjetas de productos tienen detalles o notas adicionales, como "Nuevo" o "Stock bajo", que se activan con una propiedad personalizada llamada --detail. Además, si un producto está en “Stock bajo”, tendrá un fondo de borde rojo intenso. Es probable que este tipo de información se renderice en el servidor y se pueda aplicar a las tarjetas a través de estilos intercalados de la siguiente manera:

 <div class="product-list">
  <div class="product-card-container" style="--detail: new">
    <div class="product-card">
      <div class="media">
        <img .../>
      <div class="comment-block"></div>
    </div>
  </div>
  <div class="meta">
    ...
  </div>
  </div>
  <div class="product-card-container" style="--detail: low-stock">
    ...
  </div>
  <div class="product-card-container">
    ...
  </div>
  ...
</div>

Con estos datos estructurados, puedes pasar valores a --detail y usar esta propiedad personalizada de CSS para aplicar los estilos:

@container style(--detail: new) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'New';
    border: 1px solid currentColor;
    background: white;
    ...
  }
}

@container style(--detail: low-stock) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'Low Stock';
    border: 1px solid currentColor;
    background: white;
    ...
  }
  
  .media-img {
    border: 2px solid brickred;
  }
}

El código anterior nos permite aplicar un chip para --detail: low-stock y --detail: new, pero es posible que hayas notado cierta redundancia en el bloque de código. Actualmente, no hay forma de consultar solo la presencia de --detail con @container style(--detail), lo que permitiría compartir mejor los estilos y reducir la repetición. Actualmente, esta función está en discusión en el grupo de trabajo.

Tarjetas del clima

En el ejemplo anterior, se usó una sola propiedad personalizada con varios valores posibles para aplicar estilos. Sin embargo, también puedes combinarlas usando y consultando varias propiedades personalizadas. Observa este ejemplo de tarjeta del clima:

Demostración de tarjetas del clima.

Para definir el estilo de los gradientes y los íconos de fondo de estas tarjetas, busca las características climáticas, como “nublado”, “lluvioso” o “soleado”:

@container style(--sunny: true) {
  .weather-card {
    background: linear-gradient(-30deg, yellow, orange);
  }
  
  .weather-card:after {
    content: url(<data-uri-for-demo-brevity>);
    background: gold;
  }
}

De esta manera, puedes aplicar diseño a cada tarjeta según sus características únicas. Sin embargo, también puedes aplicar diseño a combinaciones de características (propiedades personalizadas) con el combinador and de la misma manera que lo haces con las consultas de medios. Por ejemplo, un día nublado y soleado se vería de la siguiente manera:

@container style(--sunny: true) and style(--cloudy: true) {
    .weather-card {
      background: linear-gradient(24deg, pink, violet);
    }
  
  .weather-card:after {
      content: url(<data-uri-for-demo-brevity>);
      background: violet;
  }
}

Separación de los datos del diseño

En ambas demostraciones, hay un beneficio estructural de separar la capa de datos (el DOM que se renderizaría en la página) de los estilos aplicados. Los estilos se escriben como posibles variantes que se encuentran dentro del estilo de los componentes, mientras que un extremo podría enviar los datos que luego usaría para aplicarle un estilo al componente. Puedes usar un solo valor, como en el primer caso, actualizar el valor --detail o usar varias variables, como en el segundo caso (configurar --rainy, --cloudy o --sunny). Y lo mejor es que también puedes combinar estos valores. Si buscas --sunny y --cloudy, podrías mostrar un estilo parcialmente nublado.

La actualización de los valores de propiedades personalizadas a través de JavaScript se puede realizar sin problemas, ya sea mientras se configura el modelo DOM (es decir, mientras se compila el componente en un framework) o se actualiza en cualquier momento con <parentElem>.style.setProperty('--myProperty’, <value>). I

Esta es una demostración que, en unas pocas líneas de código, actualiza el --theme de un botón y aplica estilos con consultas de estilo y esa propiedad personalizada (--theme):

Aplica diseño a la tarjeta con consultas de diseño. El código JavaScript que se usa para actualizar los valores de la propiedad personalizada es el siguiente:

const themePicker = document.querySelector('#theme-picker')
const btnParent = document.querySelector('.btn-section');

themePicker.addEventListener('input', (e) => {
  btnParent.style.setProperty('--theme', e.target.value);
})

Las funciones detalladas en este artículo son solo el comienzo. Las consultas de contenedores ofrecen más beneficios que te ayudarán a crear interfaces dinámicas y responsivas. En cuanto a las consultas de estilo, aún hay algunos problemas abiertos. Una es la implementación de consultas de estilo para estilos de CSS más allá de las propiedades personalizadas. Esta opción ya forma parte del nivel de especificaciones actual, pero aún no se implementó en ningún navegador. Se espera que la evaluación de contexto booleano se agregue al nivel de especificación actual cuando se resuelva el problema pendiente, mientras que la consulta de rango se planifica para el siguiente nivel de la especificación.