Статические маркетинговые страницы и сайты документации по-прежнему поставляют горы рукописного HTML. До недавнего времени стилизовать родителя по состоянию потомка означало лишний JavaScript или неуклюжие обёртки-классы. Реляционный псевдокласс :has() переворачивает логику: секция может сменить рамку, когда внутри отмечен чекбокс, или строка формы подсветится при невалидном поле — без бандлера и часто без единой строки JS. В 2026 году покрытие Safari 15.4+ достаточно широко, чтобы многие команды использовали :has() для визуальных подсказок, если вы проверяете в настоящем WebKit и контролируете сложность селекторов. Руководство описывает практические шаблоны, матрицу решений относительно JS и container queries, а также встраивание проверок в рабочий процесс QA Safari на арендованном Mac mini.
Что :has() даёт на статических страницах
Классический CSS мог стилизовать потомков по предкам (.theme-dark .card), но не наоборот. Продуктовые команды обходили ограничение дублирующими классами в React или зеркалированием состояния детей в data-*. На статических сайтах без JS — юридические тексты, микросайты API, лендинги мероприятий — это мешало тонкой полировке UX. :has() формулирует намерение напрямую: «эта карточка в ошибке, потому что содержит .error-text». Команды доступности по-прежнему ждут видимые подписи и ARIA где нужно; :has() отвечает за оформление, а не за замену семантики.
Поскольку :has() декларативен, дизайнеры пробуют селекторы в CodePen и отгружают те же правила через Eleventy или Hugo без гидратации. Плата — когнитивная: реляционные селекторы сильны и легко перегружаются вложенностью. Введите правило вроде «не больше двух комбинаторов после :has() в маркетинговых шаблонах», чтобы будущие сопровождающие уверенно пользовались grep.
Не привязывайте :has() к корням порталов, которые часто монтируются и снимаются; даже на статике оверлей поиска меняет DOM. Держите корни :has() у карточек, групп полей и стабильных секций таблиц. Многоязычные сайты проверяйте на каждом языке: длина перевода влияет на совпадения селекторов.
Шаблоны синтаксиса
Начните с автономных компонентов. Группа полей, которая светится при фокусе любого контрола:
.field-group:has(:focus-visible) {
box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.35);
}
Строка таблицы, помечающая отсутствующие переводы, если в ячейке стоит токен-заглушка:
tr:has(td[data-missing="true"]) {
background: rgba(255, 59, 48, 0.08);
}
Сочетайте с :not() для исключающих состояний, помня, что пустое значение в чистом CSS выражается слабее, чем в Constraint Validation API. Если бизнес-правила шире атрибутных селекторов, добавьте короткий скрипт с классами вместо десятиусловной цепочки :has().
В тёмной теме :has() можно сочетать с color-scheme и prefers-reduced-motion, ослабляя тени, когда активен дочерний медиаэлемент и пользователь просит меньше движения. Следите за порядком каскада относительно медиазапросов.
Хронология Safari и заметки по тестам
- Safari 15.4 (март 2022) выпустил :has() на macOS 12.3 и iOS/iPadOS 15.4, в тон с поддержкой уровня Chromium 105 в других движках.
- Корпоративные политики всё ещё замораживают macOS до 12.3; если даже 3–4 % выручки идёт с таких сборок, дайте запасной цвет рамки без :has().
- Исправления WebKit иногда сначала попадают в Safari Technology Preview; при багах с nth-child внутри :has() сравнивайте стабильную версию и STP и перепроверяйте каждый ежемесячный статический релиз.
Зафиксируйте минимальный Safari в README рядом с Node и pnpm, чтобы подрядчики не «чинили» вёрстку, вырезая :has() на неделе дедлайна. Пересматривайте строку каждые шесть месяцев по мере обновления парка Mac.
С CDN-canary можно подмешивать упрощённый CSS старым Safari вместо дублирования HTML. Считайте :has() улучшением: без него контент должен оставаться читаемым, а формы — отправляемыми.
Матрица: :has(), JS или @container
| Задача | Предпочесть | Почему |
|---|---|---|
| Подсветить родителя при невалидном ребёнке | :has(:invalid) | Ноль JS, работает офлайн в статическом HTML. |
| Перестроить дорожки сетки при смене ширины сайдбара | @container | Ширинный лейаут — зона container queries, не :has(). |
| Отправить JSON формы и показать ошибки сервера | JavaScript | Сеть и ARIA live выходят за пределы CSS. |
| Показать иконку, если отмечен любой чекбокс | :has(:checked) | Декларативно и доступно при корректных label. |
| Дросселировать аналитику глубины прокрутки | JavaScript | CSS не может безопасно слать маяки. |
Когда уместны и :has(), и @container, разделите роли: контейнеры для макро-лейаута, :has() для микросостояний внутри дерева компонента. Навешивать оба плотно на один элемент редко помогает читаемости.
Вместе с container queries схема «кто реагирует на ширину родителя, кто на состояние детей» ускоряет ревью.
Производительность и гигиена селекторов
Браузер пересчитывает реляционные селекторы при изменении потомков. На длинной странице с 4000+ узлов шаблон body:has(.modal[open]) { overflow: hidden; } обычно терпим, но цепочки :has() на частых hover-анимациях — нет. Инвалидация стилей WebKit эффективна для локальных поддеревьев; держите корни :has() рядом с карточками и секциями форм. Если в Performance фиолетовые полосы пересчёта шире 2–3 мс на ноутбуке среднего класса во время «шторма hover», вынесите один класс на обёртку с делегированным слушателем.
Stylelint с лимитами специфичности предотвращает «суп из селекторов». Шаг CI на облачном Mac ловит регрессии только macOS до слияния; аренда Apple Silicon на две недели дешевле пятого MacBook под кампанию.
Безопасность: :has() не читает произвольный текст — только структуру и псевдоклассы, сам по себе это не канал утечки. Не логируйте полные строки селекторов с пользовательскими классами в стороннюю аналитику.
При миграции BEM-модификаторов вроде .card--error спланируйте два спринта перекрытия: класс для аналитики, визуал через :has(), затем удаление модификатора после переезда событий на data-атрибуты. Статические сайты часто экономят 2–4 КБ gzip, убрав лишние toggle-скрипты.
Для печати с :has() отдельно проверьте предпросмотр печати Safari — PDF-архивы могут терять красную рамку на белом фоне.
Чеклист QA на облачном Mac mini
Арендуйте macOS со стабильным Safari, грузите статическую сборку через file:// или локальный сервер и пройдите три сценария: только клавиатура в формах, принудительная невалидность, масштаб 200 % на обрезку. Для баг-башей хватит записи 1280×720 без HAR. Без локальных Mac схема SSH + периодический VNC совпадает с другими статьями Safari.
Mac mini на Apple Silicon остаётся тихим при параллельном линте и профилировании стилей — удобно, если ночью прогоняете двадцать HTML-шаблонов. Облачный доступ позволяет подрядчикам делить одну машину вместо пересылки ноутбуков.
Добавьте дымовой автотест: загрузить страницу, переключать примеры полей между валидным и невалидным, снимать панель вычисленных стилей для корней :has(). PNG рядом с тегом релиза почти не занимают места, но экономят 25–35 минут споров, когда WebKit расходится с Chromium.
FAQ
Какая версия Safari включает :has()?
Safari 15.4 на macOS 12.3 и iOS 15.4 (март 2022). Команды с Safari 14 нуждаются в запасном варианте без :has().
Может ли :has() заменить JavaScript в формах?
Для чисто визуальных подсказок (рамки, иконки) часто да; для текста валидации, ARIA live или серверных кругов всё ещё нужны JS или семантика HTML.
Вредит ли :has() производительности?
Глубоко вложенные цепочки на огромных страницах могут удорожать пересчёт стилей. Локализуйте селекторы, не вешайте :has() на каждый hover маркетинговой страницы на 5k узлов и измеряйте в Web Inspector.
При дисциплине :has() убирает шаблонный код из статического HTML и сохраняет тонкие бандлы. Сочетайте реальные сессии Safari, а не только Chromium, чтобы ловить особенности инвалидации WebKit. Mac mini на Apple Silicon даёт нативный WebKit, низкое энергопотребление в простое и тихую работу для длинных ручных QA. MacHTML сдаёт физические Mac mini с SSH/VNC, чтобы поднять WebKit-лабораторию на окно релиза и потом снизить масштаб — без CAPEX на железо, простаивающее между запусками.
Нужен Safari для QA :has()?
Арендуйте Mac mini на Apple Silicon, запускайте Web Inspector на настоящем WebKit и продолжайте поставлять статический HTML из привычного редактора.