Стилизация на основе контента

Веб-функции: :has() :not()

Исторически сложилось так, что для применения различных макетов к компоненту на основе его содержимого требовался либо JavaScript, либо условная логика на языке шаблонов HTML для внедрения классов-модификаторов (таких как .card--has-image или .card--text-only).

Псевдокласс :has() устраняет эту необходимость, действуя как родительский селектор. Он позволяет условно стилизовать элемент контейнера в зависимости от наличия или отсутствия определенных дочерних элементов.

С помощью :has() вы можете легко определить различные варианты макета полностью в CSS на основе фактического содержимого DOM компонента. Вы также можете комбинировать его с :not(), чтобы явно нацеливаться на отсутствие контента и определять макеты по умолчанию.

Как реализовать стилизацию контейнеров на основе контента

ОБЯЗАТЕЛЬНО. Чтобы определить наличие определенного дочернего контента, необходимо использовать селектор :has() в элементе контейнера.

Чтобы создать компонент, макет которого меняется в зависимости от контента, выполните следующие действия:

  1. Определите стиль по умолчанию. Примените базовые стили макета к элементу контейнера (например, простой одноколоночный стек).
  2. Примените переопределения на основе контента. Настройте таргетинг на контейнер с помощью :has([child-selector]) и примените новые стили макета для случаев, когда этот контент присутствует (например, многоколоночная сетка).

Пример: компонент карточки, который переключается на параллельный макет, если присутствует изображение.

/* 1. Define the default state on the component container */
/* This applies when there is NO image */
.article-card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1.5rem;
  border: 1px solid #ccc;
  border-radius: 8px;
}

/* 2. Apply styles when a specific child element is present */
/* MANDATORY: Target the container and use :has() to check for the descendant */
.article-card:has(img) {
  /* Change the layout to a row if an image exists */
  flex-direction: row;
  align-items: center;
}

/* Optional: Style the image itself (no :has() needed here) */
.article-card img {
  width: 150px;
  height: auto;
  border-radius: 4px;
}

/* You can also combine :has() and :not() to explicitly target the ABSENCE of content */
/* This selects a card that DOES NOT have an image */
.article-card:not(:has(img)) {
  /* E.g., apply a different background color for text-only cards */
  background-color: #f9f9f9;
}
<!-- Assume an <h1> precedes these components in the document layout -->
<!-- A card WITH an image (will use row layout) -->
<article class="article-card">
  <img src="thumbnail.jpg" alt="Article thumbnail" />
  <div class="content">
    <h2>Card With Image</h2>
    <p>This card lays out its content horizontally.</p>
  </div>
</article>

<!-- A card WITHOUT an image (will use default column layout) -->
<article class="article-card">
  <div class="content">
    <h2>Text-Only Card</h2>
    <p>This card lays out its content vertically, and gets its background color from the :not(:has()) rule.</p>
  </div>
</article>

Совет по повышению производительности. При использовании :has() ограничьте селектор наиболее конкретным контейнером компонента (например, .article-card). Не привязывайте его к элементам очень высокого уровня, таким как body:has(img), если изменения стиля сильно локализованы, поскольку широкие запросы :has() могут вызвать больше перерасчетов макета.

Резервные стратегии

Если стиль макета на основе контента критически важен для удобства пользователей или дизайна страницы, необходимо предоставить резервный вариант для браузеров, которые не поддерживают селектор :has(). Для чисто декоративных эффектов :has() можно использовать как прогрессивное улучшение без резервного варианта.

ОБЯЗАТЕЛЬНО. При реализации резервного варианта для критически важных макетов необходимо использовать @supports not selector(:has(*)) в CSS, чтобы определить традиционный резервный вариант на основе классов (например, .has-image).

В отличие от интерактивного стиля на основе состояния, наличие контента обычно известно во время отрисовки. Самый надежный резервный вариант – настроить серверный механизм шаблонов (или генератор статических сайтов) так, чтобы он добавлял класс, например .has-image, в контейнер, если в данных есть дочерний элемент (например, изображение).

Если отрисовка на стороне сервера невозможна, используйте небольшой скрипт с CSS.supports(), чтобы обнаружить контент и добавить класс при загрузке или после внедрения динамического контента.

/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
  /* Define a traditional modifier class that applies the exact same layout overrides */
  .article-card.has-image {
    flex-direction: row;
    align-items: center;
  }
  
  .article-card:not(.has-image) {
    background-color: #f9f9f9;
  }
}
/* Fallback JavaScript for older browsers (if not using SSR to add the class) */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
  // Find all components that need checking
  const cards = document.querySelectorAll('.article-card');
  
  cards.forEach(card => {
    // If the critical content exists, manually add the fallback class
    if (card.querySelector('img')) {
      card.classList.add('has-image');
    }
  });
}