Selectores avanzados
Combinadores
Los combinadores describen la relación entre dos selectores: no apuntan a un elemento aislado, sino a uno en función de su posición respecto a otro en el árbol del documento. Son cuatro: espacio, >, + y ~.
Descendiente (espacio)
El espacio entre dos selectores significa "el segundo dentro del primero, a cualquier profundidad":
/* Cualquier <a> que esté dentro de .navegacion, sea a cualquier nivel */
.navegacion a {
text-decoration: none;
color: inherit;
}
/* Cualquier <strong> dentro de un <li> */
li strong {
color: #84ba3f;
}
<nav class="navegacion">
<ul>
<li><a href="/">Inicio</a></li> <!-- afectado -->
<li><a href="/blog">Blog</a></li> <!-- afectado -->
</ul>
</nav>
<a href="/otro">Fuera del nav</a> <!-- NO afectado -->
Es el combinador más usado, pero también el más impreciso: selecciona todos los descendientes sin importar cuántos niveles haya entre ellos. En estructuras complejas puede aplicarse a más elementos de los previstos.
Hijo directo (>)
El signo > limita la selección al hijo inmediato, sin descender más:
/* Solo los <li> directamente dentro de .menu, no los anidados */
.menu > li {
display: inline-block;
}
/* Solo el primer nivel de párrafos en .contenido */
.contenido > p {
margin-bottom: 1.5rem;
}
<ul class="menu">
<li>Inicio</li> <!-- afectado -->
<li>Productos
<ul>
<li>Categoría A</li> <!-- NO afectado -->
</ul>
</li> <!-- afectado (el <li> exterior) -->
</ul>
Útil en menús multinivel donde el primer nivel tiene un estilo y los submenús otro distinto. Con el combinador de descendiente, el estilo se filtraría a todos los niveles.
Adyacente (+)
El signo + selecciona el elemento que aparece inmediatamente después de otro, siendo ambos hijos del mismo padre:
/* El párrafo que sigue inmediatamente a un h2 */
h2 + p {
font-size: 1.1rem;
color: #555;
}
/* La etiqueta <label> que sigue a un <input> de tipo checkbox */
input[type="checkbox"] + label {
cursor: pointer;
padding-left: 0.5rem;
}
<h2>Título</h2>
<p>Este párrafo sigue al h2.</p> <!-- afectado -->
<p>Este no sigue al h2.</p> <!-- NO afectado -->
Solo afecta al elemento inmediatamente siguiente, no a todos los que vengan después. Si entre el h2 y el párrafo hubiera otro elemento (un div, por ejemplo), el párrafo ya no quedaría seleccionado.
Hermano general (~)
La virgulilla ~ selecciona todos los elementos que vienen después (no solo el inmediato), siempre que sean hijos del mismo padre:
/* Todos los <p> que vengan después de un <h2>, en el mismo nivel */
h2 ~ p {
margin-left: 1rem;
}
/* Todos los elementos que siguen a un input:checked */
input:checked ~ .opciones-extra {
display: block;
}
<h2>Título</h2>
<p>Primer párrafo.</p> <!-- afectado -->
<div>Un div.</div>
<p>Segundo párrafo.</p> <!-- afectado -->
La combinación input:checked ~ .elemento es la base de técnicas CSS puras de mostrar/ocultar contenido sin JavaScript: cuando el checkbox está marcado, el selector activa estilos en los elementos hermanos que le siguen.
Selectores de atributo
Los selectores de atributo apuntan a elementos según el valor de sus atributos HTML. Son más precisos que los selectores de elemento y no requieren añadir clases al marcado.
La sintaxis básica es el nombre del atributo entre corchetes:
/* Cualquier elemento que tenga el atributo href (sin importar el valor) */
[href] { color: blue; }
/* Cualquier input que tenga el atributo disabled */
[disabled] { opacity: 0.5; cursor: not-allowed; }
Para filtrar por el valor exacto, se añade ="valor":
/* Solo los inputs de tipo email */
input[type="email"] { padding-right: 2rem; }
/* Solo los links que abren en nueva pestaña */
a[target="_blank"] { ... }
Además del operador de igualdad exacta, hay seis operadores para hacer coincidir partes del valor:
| Operador | Significado | Ejemplo |
|---|---|---|
[attr="val"] |
Valor exactamente igual a "val" | [type="submit"] |
[attr^="val"] |
Valor empieza por "val" | [href^="https"] |
[attr$="val"] |
Valor termina en "val" | [href$=".pdf"] |
[attr*="val"] |
Valor contiene "val" en cualquier posición | [href*="ejemplo"] |
[attr~="val"] |
Valor es lista de palabras separadas por espacios y una de ellas es "val" | [class~="activo"] |
[attr|="val"] |
Valor es exactamente "val" o empieza por "val-" | [lang|="es"] |
Algunos ejemplos prácticos que ilustran cuándo son útiles:
/* Añadir icono a los PDFs sin tocar el HTML */
a[href$=".pdf"]::after {
content: " ↓";
font-size: 0.8em;
color: #888;
}
/* Marcar todos los links externos */
a[href^="http"]::after {
content: " ↗";
}
/* Inputs de texto, email, search, url (todos los que admiten texto libre) */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]) {
border: 1px solid #ccc;
padding: 0.5rem;
}
/* Selector de idioma: tanto lang="es" como lang="es-MX" */
[lang|="es"] {
font-family: 'Segoe UI', sans-serif;
}
Los selectores de atributo tienen la misma especificidad que una clase (0-1-0), igual que los selectores de pseudoclase.
Combinando selectores
Los combinadores y los selectores de atributo se pueden encadenar con selectores de elemento, clase e ID para construir reglas muy precisas:
/* Un <a> con clase .activo dentro de .navegacion */
.navegacion a.activo {
font-weight: bold;
color: #84ba3f;
}
/* El primer <p> inmediatamente después de un h3 dentro de .articulo */
.articulo h3 + p {
margin-top: 0.5rem;
}
/* Links a PDF dentro de la tabla de descargas */
.tabla-descargas a[href$=".pdf"] {
color: #c0392b;
}
/* Todos los campos de formulario deshabilitados dentro de .formulario-bloqueado */
.formulario-bloqueado input[disabled],
.formulario-bloqueado select[disabled],
.formulario-bloqueado textarea[disabled] {
background: #f0f0f0;
}
La clave es no abusar de la especificidad. Un selector como .nav > ul > li > a.activo funciona, pero es frágil: cualquier cambio en la estructura HTML lo rompe. Cuando la cadena de combinadores se alarga, suele ser señal de que conviene añadir una clase al elemento final y usar un selector más corto.
Recapitulación
- El combinador de descendiente (espacio) selecciona elementos a cualquier profundidad dentro del padre. El de hijo directo (
>) limita la selección al primer nivel. - El adyacente (
+) selecciona solo el elemento inmediatamente siguiente al mismo nivel. El hermano general (~) selecciona todos los que vienen después al mismo nivel. - Los selectores de atributo (
[attr]) apuntan a elementos por la presencia o valor de sus atributos, sin requerir clases en el HTML. - Los operadores de atributo permiten coincidencias parciales:
^=(empieza por),$=(termina en),*=(contiene),~=(palabra en lista),|=(valor o valor con guion). - Los selectores de atributo tienen especificidad (0-1-0), igual que las clases.
- Los combinadores y atributos se encadenan con otros selectores. Cadenas largas de combinadores indican que conviene simplificar añadiendo una clase al elemento destino.
Tu proyecto
Refina los selectores CSS de estilos.css aprovechando combinadores y atributos para no añadir clases innecesarias al HTML:
/* Solo los enlaces dentro de nav, sin clase extra */
.nav-principal > a {
text-decoration: none;
color: inherit;
}
/* El primer hijo de cada sección recibe menos margen superior */
main section > *:first-child {
margin-top: 0;
}
/* El enlace mailto del footer con icono implícito */
a[href^="mailto:"] {
font-style: italic;
}
/* Imagen del proyecto: solo las que están dentro de .tarjeta */
.tarjeta > img {
display: block;
width: 100%;
}
Prueba en el inspector: selecciona uno de estos elementos y confirma en "Computed styles" que las reglas nuevas se están aplicando.
En la próxima lección: pseudoclases y pseudoelementos: estilos según el estado del elemento (hover, focus, validación) y partes virtuales del DOM.