• Sobre mí
  • Proyectos
  • Contacto
  • Blog

Apasionado por el desarrollo web y el diseño de interfaces intuitivas.

Cada error es un paso. Cada línea, un aprendizaje.

📍   Salou, Tarragona, España ✉️   [email protected] 📞   +34 641932611

Curriculum

Ver mi curriculum completo

Mapa del sitio

  • Inicio
  • Blog
  • Proyectos
  • Sobre mi
  • Mi misión
  • Sitemap

Contácto

  • Página de contácto
  • LinkedIn
  • GitHub

2026 Frankuxui - Todos los derechos reservados

  • Términos y condiciones
  • Política de privacidad
  • Política de cookies
Fotografía de unsplash creada por Alec Krum
Fotografía de unsplash creada por Alec Krum
  • CSS

Articulo publicado el 8 de julio de 2026

Por Frank Esteban Isdray Junco

El selector :has() en CSS: guía práctica para estilizar según el contenido

Aprende a usar el selector de relación :has() para aplicar estilos en función de los hijos, el estado y el contexto, sin recurrir a JavaScript.

Por qué :has() cambia la forma de escribir CSS

Durante años, CSS solo permitía seleccionar hacia abajo y hacia los lados: podías apuntar a los descendientes de un elemento o a sus hermanos posteriores, pero nunca a un elemento padre en función de lo que contiene. Cada vez que necesitábamos "si esta tarjeta tiene una imagen, cambia el layout de la tarjeta" acabábamos añadiendo una clase con JavaScript.

El selector :has() rompe esa limitación. Es un selector de relación que se evalúa sobre el elemento al que se aplica, pero cuya condición depende de lo que hay dentro o después de ese elemento. Coloquialmente se le llama el "selector padre", aunque en realidad es mucho más que eso: permite estilizar un elemento según su contenido, su estado o su contexto.

:has() es una pseudo-clase funcional que representa un elemento si cualquiera de los selectores relativos que se le pasan como argumento coincide con al menos un elemento.

En este artículo veremos la sintaxis real, patrones que resuelven problemas cotidianos, sus implicaciones de rendimiento y accesibilidad, y los casos en los que conviene evitarlo.

Sintaxis y modelo mental

La regla básica es sencilla: escribes el elemento objetivo, y dentro de :has() un selector relativo que describe la condición.

css
/* Estiliza el <figure> SOLO si contiene un <figcaption> */
figure:has(figcaption) {
  border: 1px solid var(--border, #e2e2e2);
  padding: 0.5rem;
}

La clave está en entender que el elemento estilizado es el figure, no el figcaption. El argumento de :has() es un selector relativo, lo que significa que puedes usar combinadores como >, + o ~ empezando por el elemento objetivo.

Combinadores dentro del argumento

css
/* El artículo tiene un hijo directo <img> */
article:has(> img) {
}

/* El label va seguido inmediatamente de un input inválido */
label:has(+ input:invalid) {
}

/* La sección contiene, en cualquier nivel, un elemento marcado */
section:has(.destacado) {
}

Combinar condiciones

Puedes pasar varios selectores separados por comas, lo que funciona como un OR lógico. Para un AND, encadena varios :has() o combina condiciones dentro del mismo argumento.

css
/* OR: la tarjeta tiene una imagen O un vídeo */
.card:has(img, video) {
}

/* AND: la tarjeta tiene imagen Y además un badge */
.card:has(img):has(.badge) {
}

Un detalle importante sobre especificidad: :has() no aporta especificidad por sí mismo, pero sí la aportan los selectores de su interior, tomando el más específico del argumento. Esto lo diferencia de :where(), que siempre neutraliza la especificidad.

Casos de uso reales

La teoría se entiende mejor con problemas concretos que antes exigían JavaScript o marcado extra.

Formularios que reaccionan al estado de sus campos

Uno de los usos más potentes es resaltar un grupo de formulario cuando su campo es inválido, sin tocar el DOM desde JS.

css
.form-field:has(input:invalid:not(:placeholder-shown)) {
  --field-color: #d92d20;
}

.form-field:has(input:invalid:not(:placeholder-shown)) label {
  color: var(--field-color);
}

.form-field:has(input:invalid:not(:placeholder-shown)) .error-message {
  display: block;
}

El truco de :not(:placeholder-shown) evita marcar como error un campo que el usuario aún no ha tocado. Todo el estado visual del grupo depende del estado nativo del input, que es la fuente de verdad más fiable.

Layouts adaptables según el contenido

Piensa en una tarjeta que debe cambiar de disposición cuando incluye imagen:

css
/* Tarjeta a una columna por defecto */
.card {
  display: grid;
  gap: 1rem;
}

/* Si la tarjeta tiene imagen, pasa a dos columnas */
.card:has(> img) {
  grid-template-columns: 160px 1fr;
}

Antes esto requería una clase card--with-image calculada en el servidor o en el cliente. Ahora el propio contenido dicta el layout.

Menús, drawers y bloqueo de scroll

Un patrón habitual: bloquear el scroll del documento cuando hay un modal o un menú abierto. Con :has() puedes reaccionar al estado de un elemento profundo desde la raíz.

css
/* Si en cualquier parte del body hay un dialog abierto,
   bloquea el scroll del documento */
body:has(dialog[open]) {
  overflow: hidden;
}

Esto elimina la típica clase .no-scroll que se añadía y olvidaba quitar, una fuente clásica de bugs.

Estilizar el hermano anterior

CSS nunca tuvo un "combinador de hermano anterior". Con :has() lo emulas de forma natural:

css
/* Marca el label cuyo checkbox asociado está activado */
label:has(+ input[type="checkbox"]:checked) {
  font-weight: 600;
}

Cuándo conviene usarlo y cuándo no

:has() es tentador, pero no todo problema es un clavo para este martillo.

Conviene usarlo cuando:

  1. El estilo depende del contenido real del elemento (tiene imagen, tiene error, tiene un hijo concreto).
  2. Quieres reaccionar a un estado nativo del DOM (:checked, :invalid, [open]) sin escribir JavaScript.
  3. Necesitas apuntar a un ancestro o a un hermano anterior, algo imposible con los combinadores clásicos.

Conviene evitarlo cuando:

  1. El estado ya lo gestionas en JavaScript y añadir una clase es más legible y explícito para tu equipo.
  2. La condición es muy amplia y ambigua (por ejemplo *:has(...) sobre todo el documento), porque complica el mantenimiento.
  3. Necesitas dar soporte a navegadores muy antiguos sin un fallback razonable.

Una nota sobre progressive enhancement

:has() está ampliamente soportado en los navegadores modernos (Chrome, Edge, Safari y Firefox lo implementan desde 2023). Aun así, trátalo como una mejora progresiva: la interfaz debe seguir siendo usable sin él. Puedes detectar el soporte con @supports selector().

css
@supports selector(:has(*)) {
  .card:has(> img) {
    grid-template-columns: 160px 1fr;
  }
}

Puedes consultar el estado de soporte actualizado en MDN.

Rendimiento y accesibilidad, en breve

Sobre rendimiento, los motores modernos optimizan :has() razonablemente bien, pero conviene ser específico: prefiere .card:has(> img) frente a un :has() sin acotar que obligue al navegador a evaluar árboles grandes. Sobre accesibilidad, recuerda que :has() solo cambia estilos: no altera el árbol de accesibilidad ni sustituye a los atributos ARIA. Si un cambio visual comunica información (por ejemplo, un estado de error), asegúrate de que también existe a nivel semántico con aria-invalid, role o mensajes asociados.

Conclusión

El selector :has() traslada a CSS decisiones que antes vivían obligatoriamente en JavaScript: estilizar en función del contenido, del estado nativo del DOM o del contexto. El resultado es un CSS más declarativo, con menos clases sincronizadas a mano y menos puntos de fallo. Empléalo con criterio, mantén los selectores específicos y consérvalo como una capa de mejora progresiva, y ganarás interfaces más robustas con bastante menos código.

En este artículo

  1. Por qué :has() cambia la forma de escribir CSS
  2. Sintaxis y modelo mental
  3. Combinadores dentro del argumento
  4. Combinar condiciones
  5. Casos de uso reales
  6. Formularios que reaccionan al estado de sus campos
  7. Layouts adaptables según el contenido
  8. Menús, drawers y bloqueo de scroll
  9. Estilizar el hermano anterior
  10. Cuándo conviene usarlo y cuándo no
  11. Una nota sobre progressive enhancement
  12. Conclusión

Articulos relacionados

Estos articulos pueden interesarte si te gusto este articulo.

Fotografía de Unsplash creada por Alessio Furlan
Imagen del articulo Configurando Tailwind CSS 4 en Next.js
27 de junio de 2026
Next.js
·Tailwind CSS
·CSS

Tailwind CSS 4 en Next.js: configuración paso a paso

Guía completa para integrar Tailwind CSS 4 en un proyecto de Next.js utilizando la configuración oficial recomendada, optimizada para rendimiento y escalabilidad