2 minute read

Сложные селекторы в CSS, особенно :has() и :contains() (которого нет в CSS), часто путают.

✅ 1. :has()есть в CSS, работает в новых браузерах

Позволяет выбрать родителя, если внутри есть определённый элемент

📌 Пример:

css CopyEdit div:has(.carousel-item) {
  border: 1px solid red;
}

Что делает:

Выделит все div, в которых есть элемент с классом .carousel-item.

🎯 Пример твоей задачи: Найти контейнер-карусель

Допустим, HTML такой:

<div>
  <div class="carousel-item">1. Pick Your Artwork</div>
  <div class="carousel-item">2. Pick Your Size</div>
  <div class="carousel-item">3. Install Easily</div>
</div>

Ты хочешь применить стили к родителю этих .carousel-item, даже если он не имеет никакого класса:

div:has(> .carousel-item) {
  background-color: #f0f0f0;
}
  • > — ищет только прямых детей
  • Без > — найдёт любого потомка

🚫 2. :contains()НЕ существует в CSS

Это был старый CSS3 draft, но так и не вошёл в стандарт.

❌ Не работает:

div: contains("1. Pick Your Artwork"); /* НЕ РАБОТАЕТ */

💡 Альтернатива: использовать :has() с селектором и обработать структуру.

Если ты не можешь ориентироваться по классу, но можешь по тексту — тогда CSS уже не поможет, и нужно использовать JavaScript или jQuery.

✅ JavaScript альтернатива по тексту

document.querySelectorAll("div").forEach((div) => {
  if (div.textContent.includes("1. Pick Your Artwork")) {
    const parent = div.closest("div"); // получаем контейнер
    parent.style.background = "lightyellow";
  }
});

💡 Общие примеры :has()

CSS-селектор Что делает
section:has(h2) Все section, внутри которых есть h2
div:has(.error-message) Все div, у которых внутри есть элемент с классом
ul:has(li.selected) Все ul, содержащие активный элемент
.card:has(img[src*="logo"]) Все .card, где изображение — это логотип

🧪 Поддержка :has() (на 2025 год):

Браузер Поддержка
Chrome 105+
Edge 105+
Safari 15.4+
Firefox ❌ (в процессе разработки)

🧠 Firefox пока не поддерживает :has(), так что в критичных случаях — fallback или JS.


Да, можно! В CSS селекторе :has() пока нет встроенной поддержки подсчёта количества элементов, типа :has(> .carousel-item:count(3)) — такого не существует.

Но есть обходной путь с помощью селекторов :nth-child() и комбинации :not().

Вот такой HTML:

<div class="carousel">
  <div class="carousel-item">1</div>
  <div class="carousel-item">2</div>
  <div class="carousel-item">3</div>
</div>
div:has(
    > .carousel-item:nth-child(1):not(:nth-child(4)),
    > .carousel-item:nth-child(2),
    > .carousel-item:nth-child(3)
  ):not(:has(> .carousel-item:nth-child(4))) {
  border: 2px solid red;
}

Что делает:

  • Удостоверяется, что есть .carousel-item на позициях 1, 2, 3
  • И нет .carousel-item на позиции 4
  • Тем самым условно проверяет, что .carousel-item ровно 3

⚠️ Ограничения:

  • Это жёстко завязано на порядок элементов
  • Не работает, если среди детей есть другие, не .carousel-item

🧠 Надёжный способ — через JavaScript

Если тебе действительно нужно условие “ровно три”, JS даст куда больше контроля:

document.querySelectorAll("div").forEach((div) => {
  const items = div.querySelectorAll(":scope > .carousel-item");
  if (items.length === 3) {
    div.style.border = "2px solid green";
  }
});

✅ Что делает:

  • :scope > .carousel-item — только прямые дети
  • Если таких ровно три, тогда стили применяются

Вот этот фрагмент:

div:has(
    > .carousel-item:nth-child(1):not(:nth-child(4)),
    > .carousel-item:nth-child(2),
    > .carousel-item:nth-child(3)
  ):not(:has(> .carousel-item:nth-child(4))) {
  border: 2px solid red;
}

Можно упростить до:

div:has(
    > .carousel-item:nth-child(1),
    > .carousel-item:nth-child(2),
    > .carousel-item:nth-child(3)
  ):not(:has(> .carousel-item:nth-child(4))) {
  border: 2px solid red;
}

Это работает, если:

  • .carousel-item стоят подряд с начала
  • И нет четвёртого элемента

Но почему в некоторых примерах добавляют :not(:nth-child(4))?

Это делают:

  • Для страховки, если селектор должен быть строго детерминирован по позиции
  • Либо по ошибке — избыточная защита от четвертого, которая уже есть во второй части селектора

Вывод:

✅ Да, :not(:has(...nth-child(4))) уже исключает лишние элементы.

:not(:nth-child(4)) у :nth-child(1) избыточен в этом контексте.