Skip to content

CSS — Современные селекторы

💻 :has() — parent selector

Выбрать родителя по наличию дочернего элемента. Меняет подход к CSS.

css
/* Карточка с изображением — другой layout */
.card:has(img) {
  display: grid;
  grid-template-columns: 200px 1fr;
}

/* Форма с ошибкой */
.form:has(.error) {
  border: 1px solid red;
}

/* Параграф после заголовка */
h2 + p { }           /* следующий сосед — старый способ */
:has(h2) > p { }     /* первый p внутри того что содержит h2 */

/* Input в фокусе — стилизовать label */
.field:has(input:focus) label {
  color: blue;
}

/* Навигация с открытым дропдауном */
.nav-item:has(.dropdown:hover) > a {
  color: var(--accent);
}

/* li без дочерних li (листовой элемент) */
li:not(:has(li)) {
  font-weight: normal;
}

💻 :focus-visible

Показывать outline только при навигации с клавиатуры, не при клике мышью:

css
/* Плохо — скрывает outline везде */
button:focus { outline: none; }

/* Хорошо — убирает только при клике, оставляет при Tab */
button:focus:not(:focus-visible) { outline: none; }

/* Или */
button:focus-visible {
  outline: 2px solid #3b82f6;
  outline-offset: 2px;
}

💻 :focus-within

Родитель получает стиль когда фокус внутри него:

css
/* Подсветить всю группу поля при фокусе на input */
.field:focus-within label {
  color: #3b82f6;
}

.field:focus-within .field__hint {
  opacity: 1;
}

/* Навигационный блок при фокусе */
.nav:focus-within {
  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}

💻 :placeholder-shown

Стилизовать input пока виден placeholder (то есть пока он пустой):

css
/* Скрыть label когда поле пустое (floating label паттерн) */
.field__label {
  transform: translateY(-100%);
  font-size: 0.85em;
}

input:placeholder-shown + .field__label {
  transform: translateY(0);
  font-size: 1em;
}

💻 :nth-child(An+B of .selector)

nth-child с фильтром по классу — выбирает только среди элементов с нужным классом:

css
/* Каждый чётный .card (игнорируя другие дочерние элементы) */
.card:nth-child(even of .card) {
  background: #f9fafb;
}

/* Первые три .featured */
.item:nth-child(-n+3 of .featured) {
  font-weight: bold;
}

Раньше :nth-child(even) считал все дочерние, включая div, span и другие — теперь можно фильтровать.


💻 ::marker

Стилизовать маркер списка без list-style: none и псевдоэлементов:

css
li::marker {
  color: #3b82f6;
  font-size: 1.2em;
}

/* Кастомный символ */
li::marker {
  content: "→ ";
  color: var(--accent);
}

/* Нумерованный список */
ol li::marker {
  font-weight: bold;
  font-variant-numeric: tabular-nums;
}

💻 ::backdrop

Фон за <dialog> и полноэкранными элементами:

css
dialog::backdrop {
  background: oklch(0 0 0 / 0.5);
  backdrop-filter: blur(4px);
}

/* Анимация появления backdrop */
dialog[open]::backdrop {
  animation: fade-in 0.2s ease;
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

💻 ::selection

Стиль выделенного текста:

css
::selection {
  background: oklch(0.7 0.15 250 / 0.3);
  color: inherit;
}

/* Для конкретного элемента */
.code-block::selection {
  background: #fbbf24;
  color: #000;
}

⚠️ Подводные камни

  • :has() не работает в Firefox < 121 — проверяй если нужна поддержка старых версий
  • :nth-child(of .selector) — поддержка с 2023 года, Safari 9+, Chrome 111+
  • ::marker — ограниченный набор свойств: color, content, font-*, white-space
  • :focus-visible — добавляй как дополнение к :focus, не замену (accessibility)

Built with VitePress and ❤️