:tiene() | Trucos CSS - Trucos CSS
CSS :has()
pseudoclass selecciona elementos que contienen otros elementos que coinciden con el selector pasado en sus argumentos. A menudo se le llama "selector principal" debido a su capacidad para seleccionar un elemento principal en función de los elementos secundarios que contiene y aplicar estilos al principal.
/* Select the .card element when it
contains a <figure> immediately
followed by a paragraph. */
.card:has(figure + p) {
flex-direction: row;
}
Este ejemplo selecciona un elemento con .card
clase cuando contiene un <figure>
elemento que es seguido inmediatamente por un <p>
elemento:
<article class="card">
<figure></figure>
<p></p>
</article>
Esto es extremadamente útil para escribir estilos para componentes que pueden o no contener ciertos elementos dentro, como una cuadrícula de mapa, donde un elemento de mapa siempre tiene un párrafo pero puede no tener una imagen que lo acompañe.
De esa forma, en situaciones en las que no siempre sabe lo que implica el marcado, puede escribir estilos que coincidan con esas condiciones.
:has()
se determina en Especificación de selectores de nivel 4 donde se describe como la "pseudoclase relacional" debido a su capacidad para asignar selectores en función de la relación de un elemento con otros elementos.
Uso principal
El siguiente HTML contiene dos <button>
Uno de ellos tiene un icono SVG.
<!-- Plain button -->
<button>Add</button>
<!-- Button with SVG icon -->
<button>
<svg></svg>
Add
</button>
Ahora, supongamos que desea aplicar estilos solo al <button>
quién tiene <svg>
elemento en su interior.
:has()
es perfectamente adecuado para el trabajo:
button:has(svg) {
/* Styles */
}
Él :has()
selector nos permite distinguir entre un botón que hay descendiente <svg>
y uno que no.
Sintaxis
:has( <unforgiving-relative-selector-list> )
A una lista despiadada para elegir se refiere a los argumentos que se pasan en :has()
argumentos al selector, que es una lista de elementos separados por comas que se evalúan juntos en función de su relación con el elemento principal.
article:has(ol, ul) {
/* Matches an <article> that contains either
an ordered or unordered list. */
}
Desglosaremos la naturaleza "imperdonable" de la lista de argumentos con más detalle en un momento.
especificidad
Uno de los aspectos más interesantes de :has()
es que su especificidad está determinada por el elemento más específico en su lista de argumentos。 Digamos que tenemos las siguientes reglas de estilo:
article:has(.some-class, #id, img) {
background: #000;
}
article.some-class {
background: #fff;
}
Tenemos dos reglas, las cuales eligen <article>
elemento para cambiar su fondo. ¿Qué fondo obtiene este HTML?
<article>
<div class="some-class"></div>
</article>
Podrías pensar que se está poniendo blanco (#fff
) de fondo porque viene más tarde en la cascada. Pero dado que la lista de argumentos para :has()
incluye otros selectores que debemos considerar más específico uno en la lista para determinar la verdadera especificidad de esta primera regla #id
después.
Vamos a compararlos:
article.some-class
generar un resultado de(0,1,1)
article:has(.some-class, #id, img)
generar un resultado de(1,0,1)
¡La primera regla gana! El artículo se vuelve negro (#000
) antecedentes.
¡Examen sorpresa!
¿Qué color crees que gana en el siguiente ejemplo?
article:has(h1, .title) a {
color: red;
}
article h1 a {
color: green;
}
¡Muéstrame la respuesta!
/* Specificity: (0,1,2) */
article:has(h1, .title) a {
color: red; /* 🏆 Winner! */
}
/* Specificity: (0,0,3) */
article h1 a {
color: green;
}
:has()
es un selector "implacable"
Se ha presentado el primer borrador de las especificaciones. :has()
me gusta "selector indulgente":
:has( <forgiving-relative-selector-list> )
La idea es que la lista pueda contener un selector inválido e ignorarlo.
/* Example: Do not use! */
article:has(h2, ul, ::-scoobydoo) { }
::-scoobydoo
es un pseudo-elemento inválido completamente ficticio que no existe. Si :has()
fue "perdonador", este selector falso simplemente se ignorará mientras que el resto de los argumentos se analizan normalmente.
Pero más tarde, debido a un conflicto con el comportamiento de jQuerydecidió definir los autores de la especificación :has()
como un selector implacable. Como resultado :is()
y :where()
son los únicos selectores relativos indulgentes del grupo.
Esto significa :has()
se comporta mucho más como un selector compuesto.De acuerdo a especificaciones CSSpor razones heredadas, el comportamiento general del selector compuesto es el siguiente si algún selector de la lista no es válido, la lista completa de selectores no es válidalo que hace que se descarte todo el conjunto de reglas.
/* This doesn't do anything because `::-scoobydoo`
is an invalid selector */
a, a::-scoobydoo {
color: green;
}
Lo mismo va para :has()
Cualquier selector no válido en su lista de argumentos invalidará todo lo demás en la lista. Entonces, este ejemplo que vimos antes:
/* Example: Do not use! */
article:has(h2, ul, ::-scoobydoo) { }
...no hará absolutamente nada Los tres selectores en la lista de argumentos no son válidos debido a esta invalidación ::scoobydoo
selector. Es una especie de todo o nada.
Pero hay una solución para esto. No se olvide, :is()
y :where()
son indulgentes incluso si :has()
no lo es Esto significa que podemos anidar cualquiera de los selectores :has()
para obtener un comportamiento más indulgente:
p::has(:where(a, a::scoobydoo)) {
color: green;
}
Así que si alguna vez preguntas :has()
para trabajar como un selector "perdonador", intente anidar :is()
o :where()
dentro de eso.
La lista de argumentos acepta selectores complejos.
A selector complejo contiene uno o más selectores compuestos (p. ej. a.fancy-link
) y combinadores (p. ej. >
, +
, ~
La lista de argumentos para :has()
acepta estos selectores completos y se puede utilizar para identificar relaciones entre varios elementos.
<relative-selector> = <combinator>? <complex-selector>
Aquí hay un ejemplo de un selector relativo que contiene un selector complejo con combinador de niños (>
Selecciona elementos con un .icon
clase que son hijos directos de relaciones que tienen un .fancy-link
clase y están en un :focus
estado:
a.fancy-link:focus > .icon {
/* Styles */
}
Este tipo de cosas pueden se utilizan directamente en :has()
lista de argumentos:
p:has(a.fancy-link:focus > .icon) {
/* Styles */
}
Pero en lugar de elegir .icon
elementos que son hijos directos de .fancy-class
conexiones que están en :focus
diseñamos párrafos que están enfocados .fancy-links
con hijos directos que tienen .icon
clase.
¡Uf, estoy tratando de decir eso tres veces más rápido! Puede ser útil ver un marcado de ejemplo que corresponda a:
eso generalmente no admite pseudo-selectores
yo digo esto :has()
"generalmente" no admite otros pseudoelementos en sus argumentos, porque eso es exactamente lo que dice la especificación:
Nota: Los pseudoelementos generalmente se excluyen de
:has()
ya que muchos de ellos existen condicionalmente, en base al estilo de sus antepasados, lo que permite que sean consultados por:has()
introducirá bucles.
De hecho, hay varios "pseudo-elementos permitidos" que están permitidos en el :has()
lista de argumentos La especificación ofrece el siguiente ejemplo que muestra cómo :not()
se usa junto :has()
:
/* Matches any <section> element that contains
anything that’s not a heading element. */
section:has(:not(h1, h2, h3, h4, h5, h6))
Jay Tompkins ofrece otro ejemplo Show :has()
se puede usar para diseñar formularios en función de diferentes estados de entrada, como :valid
, :invalid
y :placeholder-shown
:
label {
color: var(--color);
}
input {
border: 4px solid var(--color);
}
.form-group:has(:invalid) {
--color: var(--invalid);
}
.form-group:has(:focus) {
--color: var(--focus);
}
.form-group:has(:valid) {
--color: var(--valid);
}
.form-group:has(:placeholder-shown) {
--color: var(--blur);
}
No se puede insertar, pero admite encadenamiento.
Lo siento, no anidamos :has()
dentro de :has()
a la:
/* Nesting is a no-go */
.header-group:has(.subtitle:has(h2)) {
/* Invalid! */
}
Esto crearía un bucle infinito donde la especificación se evalúa en otra evaluación. Sin embargo, esto le permite encadenar argumentos:
h2,
.subtitle {
margin-block-end: 1.5rem;
}
/* Reduce spacing on header because the subtitle will handle it */
.header-group:has(h2):has(.subtitle) h2 {
margin-block-end: 0.2rem;
}
El encadenamiento funciona como AND
operación lógica porque ambas condiciones deben coincidir para que la regla de estilo tenga efecto. Supongamos que tiene una lista de .news-articles
y los artículos que contiene están categorizados. Tal vez quiera aplicar ciertos estilos a la lista, pero solo si contiene artículos con .breaking-news
y artículos con .featured-news
pero déjelo sin cambios si solo uno o ninguno de los artículos coincide con estas clases.
Bueno, puedes atar dos :has()
declaraciones para este estilo condicional:
.news-list:has(.featured-news):has(.breaking-news) {
/* Styles */
}
Este ejemplo es específico sólo para .news-list
Si quisiéramos hacer coincidir algún elemento padre antiguo que :has()
ambos .featured-news
y .breaking-news
clases de artículos, podríamos saltarnos .news-list
total:
:has(.featured-news):has(.breaking-news) {
/* Styles */
}
Esto es más que un selector "principal"
Jay Tompkins lo llama un selector de "familia" que puede ser una descripción más apropiada, especialmente en lo que se refiere al último ejemplo que vimos. Veámoslo de nuevo:
.header-group:has(h2):has(.subtitle) h2 {
margin-block-end: 0.2rem;
}
No solo seleccionamos un artículo con .header-group
una clase que contiene un <h2>
Estos son los poderes de elección de los padres que generalmente se atribuyen :has()
Lo que seleccionamos es un elemento:
- con
.subtitle
clase - este es un hijo de
.header-group
- y contiene un
<h2>
elemento en su interior.
Bien <h2>
el hijo directo de .header-group
No, es más como un nieto.
Combinatorio :has()
con otros pseudo-selectores relacionales
Puedes combinar :has()
con otros selectores de pseudoclases funcionales como :where(), :not() y :is().
Peinada :has()
y :is()
Por ejemplo, puede verificar si alguno de los encabezados HTML tiene al menos una <a>
elemento como descendiente:
:is(h1, h2, h3, h4, h5, h6):has(a) {
color: blue;
}
/* is equivalent to: */
h1:has(a),
h2:has(a),
h3:has(a),
h4:has(a),
h5:has(a),
h6:has(a) {
color: blue;
}
también puedes pasar :is()
como argumento para :has()
Imagínese si cambiamos el último ejemplo para que cada nivel de encabezado que contiene <a>
elemento hijo o cualquier elemento hijo con .link
clase seleccionada:
:is(h1, h2, h3, h4, h5, h6):has(:is(a, .link)) {
color: blue;
}
Combinatorio :has()
y :not()
Nosotros podemos usar :has()
con :not()
Supongamos que desea agregar un marco a un .card
elemento si no contiene uno <img>
elemento sucesor. Cosa segura:
.card:not(:has(img)) {
border: 1px solid var(--my-amazing-color);
}
Esto comprueba que la tarjeta :has()
cada imagen luego dice:
Oye, si no encuentras ninguna imagen, aplica estos estilos.
¿Quieres volverte más loco? Elijamos cada uno .post
elemento para imágenes faltantes alt
texto:
.post:has(img:not([alt])) {
/* Styles */
}
¿Ves lo que hicimos aquí? Esta vez, :not()
es en :has()
.Esto dice:
Oye, si encuentras alguna publicación que contenga una imagen sin texto alternativo, aplica esos estilos y gracias.
Esto se puede usar para depurar imágenes faltantes alt
atributo:
Aquí hay otro ejemplo que tomé prestado de vídeo de eric meierDigamos que desea seleccionar cada <div>
que no tiene mas que <img>
elementos.
div:not(:has(:not(img))) {
/* Styles */
}
Lo que estamos diciendo aquí es:
Si encuentras un
<div>
y no hay nada más que una o más imágenes, ¡haz tu magia!
Estas son situaciones donde el orden importa
Observe cómo cambiar el orden de nuestros selectores cambia lo que seleccionan. Hablamos de la naturaleza implacable de:has()
lista de argumentos, pero es aún menos indulgente en los ejemplos que hemos visto con esta combinación :has()
con otros pseudo-selectores relacionales.
Veamos un ejemplo:
article:not(:has(img)) {
/* Styles */
}
Esto coincide con cualquier <article>
elementos que no contienen imágenes. Ahora cambiemos las cosas para que :has()
viene antes :not()
:
article:has(:not(img)) {
/* Styles */
}
Ahora emparejamos cada uno <article>
elemento que contiene algo siempre y cuando no haya imágenes. El artículo debe tener un sucesor que coincida con este descendiente. puede ser cualquier cosa menos una imagen.
casos de uso
Separadores de migas de pan
Las rutas de navegación son una forma conveniente de mostrar en qué página se encuentra actualmente un usuario y dónde encaja esa página en el mapa del sitio. Por ejemplo, si está en una página "Acerca de", puede mostrar una lista que contiene un elemento con un enlace a la página de inicio y un elemento que simplemente muestra la página actual:
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="https://css-tricks.com/">Home</a></li>
<li class="breadcrumb-item current">About</li>
</ol>
Esto es genial. Pero, ¿y si queremos mostrarlo como una lista horizontal y ocultar los números de la lista? Fácil con CSS:
ol {
display: flex;
list-style: none;
}
¡Manten tu cabeza en alto! Ajustes list-style: none
evita que Safari identifique el elemento como una lista.
Bien, pero ahora nos quedan dos elementos de la lista que ocurren uno dentro del otro. Podemos agregar un gap
entre ellos, ya que estamos usando Flexbox:
ol {
display: flex;
gap: .5rem;
list-style: none;
}
Eso ciertamente ayuda. Pero podemos hacer una discriminación más fuerte entre los dos elementos agregando un separador entre ellos. Nada de miedo:
.breadcrumb-item::after {
content: "https://css-tricks.com/";
}
¡Pero espera! No necesitamos un delimitador después .current
elemento porque siempre es el último en la lista y no hay nada después de él. aquí es donde :has()
Podemos buscar a cualquier niño con .current
clase usando el siguiente combinador hijo (~
) para olerlo:
.breadcrumb-item:has(~ .current)::after {
content: "https://css-tricks.com/";
}
¡Aquí!
Validación de formularios sin JavaScript
Como aprendimos antes, :has()
no acepta pseudo-elementos, pero nos permite usar pseudo-clases. Podemos usar esto como una forma ligera de validación con la que normalmente tratamos en JavaScript.
Digamos que tenemos un formulario de registro de boletín que requiere un correo electrónico:
<form>
<label for="email-input">Add your pretty email:</label>
<input id="email-input" type="email" required>
</form>
El correo electrónico es un campo obligatorio en este formulario. De lo contrario, ¡no hay nada que enviar! Tal vez podamos agregar un borde rojo a la entrada si el usuario ingresa una dirección de correo electrónico no válida:
form:has(input:invalid) {
border: 1px solid red;
}
Intente ingresar una dirección de correo electrónico no válida, luego haga clic en el campo de contraseña de la pestaña.
Formateo de elementos completados en una lista de tareas pendientes
¿Ha intentado diseñar la etiqueta de la casilla de verificación cuando se marca la entrada? Ya sabes, como una aplicación de lista de tareas pendientes en la que completas los elementos de la lista marcando una casilla.
La estructura de su HTML podría verse así:
<form>
<input id="example-checkbox" type="checkbox">
<label for="example-checkbox">We need to target this when input is checked</label>
</form>
Aunque es mejor poner la etiqueta antes o envolverla alrededor del elemento de entrada para debido a la accesibilidaddebe colocar la etiqueta después de la entrada para poder seleccionar la etiqueta en función de la entrada check
atributo.
Usando un combinador hermano siguiente (+
), puede diseñar la etiqueta de esta manera:
/* When the input is checked,
style the label */
input:checked + label {
color: green;
}
Cambiemos el marcado y creemos una etiqueta implícita poniendo la entrada dentro:
<form>
<label>
<input type="checkbox">
We need to target this when input is checked
</label>
</form>
Antes no había forma de seleccionar esta etiqueta cuando se verificaba el inicio de sesión, pero ahora que tenemos :has()
selector, tenemos plena potencia:
/* If a label has a checked input,
style that label */
label:has(input:checked) {
color: green;
}
Ahora volvamos al marcado ideal donde la etiqueta viene antes de la entrada:
<form>
<label for="example-checkbox">We need to target this when input is checked</label>
<input id="example-checkbox" type="checkbox">
</form>
Todavía puedes usar :has()
Como el selector anterior para seleccionar y diseñar la etiqueta explícita, manteniendo un marcado más accesible:
/* If a label has a checked input
that is it's next sibling, style
the label */
label:has(+ input:checked) {
color: green;
}
Botón inteligente "Añadir al carrito".
Lo que pasa, aplicamos :has()
al elemento principal o cuerpo de la página?
:root:has( /* Any condition */ ) {
/* Styles */
}
Él :root
es el documento de nivel superior el que controla todo lo que está debajo, ¿verdad? Si algo sucede en algún lugar más abajo en el árbol DOM, puede detectarlo y dar forma a otra rama DOM en consecuencia.
Supongamos que ejecuta un sitio de comercio electrónico y desea diseñar el botón "Agregar al carrito" cuando se agrega un producto al carrito. Esto es bastante común, ¿verdad? Sitios como Amazon hacen esto todo el tiempo para informar al usuario un artículo está correctamente en su carrito.
Imagina que esta es la estructura de nuestro HTML. El botón "Agregar al carrito" se encuentra en el <header>
elemento y el producto está contenido en un <main>
elemento.
Un ejemplo de marcado súper simplificado podría verse así:
<body>
<header>
<button class="cart-button">Add to cart</button>
</header>
<main>
<ul>
<li class="p-item">Product</li>
<li class="p-item is-selected">Product</li>
<li class="p-item">Product</li>
</ul>
</main>
</body>
En CSS podemos comprobar si <body>
:has()
cualquier descendencia con ambos .p-item
y .is-selected
Una vez que esta condición es verdadera, .cart-button
se puede seleccionar:
body:has(.p-item.is-selected) .cart-button {
background-color: green;
}
Cambiar temas de color
Modo oscuro, Modo claro, Modo de alto contraste Dar a los usuarios la opción de personalizar el tema de color de un sitio puede ser una buena mejora de UX.
Digamos que en algún lugar profundo del documento tienes un <select>
menú de usuario para seleccionar un tema de color:
<select>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="high-contrast">High-contrast</option>
</select>
Nosotros podemos usar :has()
sobre <body>
elemento y verifique <select>
el menú está seleccionado <option>
Así, si uno <option>
contiene un cierto valor, podemos actualizar las propiedades personalizadas de CSS con diferentes valores de color para cambiar el tema de color actual:
body:has(option[value="dark"]:checked) {
--primary-color: #e43;
--surface-color: #1b1b1b;
--text-color: #eee;
}
Algo está pasando de nuevo (usuario <select>
es un <option>
) en algún lugar del árbol DOM y observe los cambios en el nivel superior del árbol (el <body>
) y actualice los estilos en consecuencia (a través de propiedades personalizadas).
Estilo de un elemento basado en el número de niños
Aquí viene uno inteligente a través de Brahms Van Damme. :has()
puede aplicar estilos en función del número de elementos secundarios en un contenedor principal.
Imagine que tiene un diseño con dos columnas. Si el número de elementos en la cuadrícula es os: 3, 5, 7, 9, etc., entonces le queda un espacio vacío en la cuadrícula después del último elemento.
Sería mejor si el primer elemento de la cuadrícula pudiera ocupar ambas columnas en la primera fila para evitar esto. Y para eso tendrás que comprobar si :last-child
el elemento de cuadrícula también es un hijo impar:
/* If the last item in a grid is an odd-numbered child */
.grid-item:last-child:nth-child(odd) {
/* Styles */
}
Esto se puede transferir a :has()
una lista de argumentos para que podamos diseñar la cuadrícula :first-child
por lo que ocupa toda la primera fila de la cuadrícula cuando :last-child
es un número impar:
.grid:has(> .grid-item:last-child:nth-child(odd)) .grid-item:first-child {
grid-column: 1 / -1;
}
Compatibilidad con navegador
Escritorio
Cromo | Firefox | ES DECIR | Borde, final | Safari |
---|---|---|---|---|
105 | no | no | 105 | 15.4 |
Móvil/tableta
Android cromo | android firefox | Androide | iOSSafari |
---|---|---|---|
108 | no | 108 | 15.4 |
Pruebas de soporte
Él @supports
generalmente apoya :has()
lo que significa que podemos verificar si el navegador lo admite y aplicar estilos condicionalmente según el resultado:
@supports(figure(:has(figcaption))) {
/* Supported! */
}
Deja una respuesta