В веб-разработке термин прямые потомки (direct children) обозначает элементы, вложенные в родительский контейнер ровно на один уровень глубже — без промежуточных обёрток. Это ключевое понятие для понимания DOM-дерева и точечного применения стилей через CSS-комбинатор >.

В отличие от всех потомков (descendants), которые включают в себя внуков, правнуков и более глубокие вложенности, прямые потомки — это только «дети» первого уровня. Разница критична при написании селекторов: article p затрагивает любые параграфы внутри статьи, а article > p — лишь те, что лежат непосредственно в <article>.

Как браузер строит дерево DOM и где место прямым потомкам

Когда парсер читает HTML, он создаёт иерархическую модель документа. Каждый тег становится узлом (node), а вложенность определяет родственные связи: родитель (parent), потомок (descendant), ребёнок (child) и прямой потомок (direct child). Последний термин в спецификации DOM Living Standard синонимичен «ребёнку».

Визуально представьте список <ul> с тремя <li>. Если внутри одного пункта вложен ещё один <ul>, то внешний список имеет три прямых потомка — именно эти <li>. Внутренний <ul> уже не является прямым потомком внешнего, он — потомок второго уровня (внук).

⚠️ Внимание: текстовые узлы (пробелы, переносы строк между тегами) тоже считаются дочерними узлами в DOM, но CSS-селектор > работает только с элементами (element nodes), игнорируя текстовые ноды.

CSS-комбинатор >: синтаксис, специфичность и поддержка

Селектор родитель > прямой-потомок выбирает элементы, у которых указанный родитель является непосредственным предком. Специфичность такого селектора складывается из специфичностей обеих частей: например, .menu > li имеет вес 0-1-1 (класс + тег).

Поддержка комбинатора > есть во всех браузерах, начиная с IE7. В современных движках он работает без нареканий, но в очень старых версиях Internet Explorer могли возникать баги с динамически добавленными элементами.

  • 🎯 Точечная стилизация — не задевает глубоко вложенные теги
  • 🚀 Производительность — браузеру не нужно обходить всё поддерево
  • 🧩 Модульность — идеален для БЭМ-блоков и компонентного подхода
  • 🔒 Инкапсуляция — защищает внутреннюю структуру от внешних стилей

Практические сценарии: когда нужен строгий контроль вложенности

Классический кейс — навигационное меню. Родительский <ul> должен стилизовать только свои прямые <li>, не затрагивая пункты выпадающих подменю. Селектор .nav > li решает задачу идеально, тогда как .nav li покрасит всё подряд.

Ещё один случай — таблицы. Если нужно выделить только ячейки заголовка <thead>, пишем thead > tr > th. Это гарантирует, что <th> внутри вложенных таблиц (например, в ячейке основной таблицы) не получат случайных стилей.

☑️ Когда использовать прямой комбинатор >

Выполнено: 0 / 5

Отличия от смежного селектора + и обобщённого ~

Новички часто путают > с соседним селектором + и родственным ~. Разница фундаментальна: комбинатор > работает по вертикали (родитель → ребёнок), а + и ~ — по горизонтали (брат → брат).

Пример: h2 + p выберет первый параграф сразу после заголовка (если они соседи), а h2 ~ p — все параграфы после заголовка внутри общего родителя. Ни один из них не смотрит вглубь дерева, в отличие от >.

СелекторНаправлениеЧто выбираетПример
parent > childВниз (1 уровень)Прямые детиul > li
parent childВниз (любая глубина)Все потомкиul li
prev + nextВправо (сосед)Следующий братh2 + p
prev ~ nextВправо (все братья)Все следующие братьяh2 ~ p

Псевдоклассы :first-child, :last-child, :only-child и nth-child

Эти псевдоклассы работают именно в контексте прямых потомков своего родителя. :first-child выбирает элемент, если он является первым ребёнком у своего родителя — независимо от тега. Важно: p:first-child не сработает, если перед <p> стоит <h2>, даже если это первый параграф.

Для выбора по типу используют :first-of-type, :last-of-type, :nth-of-type(n). Они учитывают только элементы указанного тега среди прямых потомков. Это спасает, когда структура смешанная: article > p:first-of-type возьмёт первый параграф, даже если перед ним есть заголовок.

⚠️ Внимание: :only-child сработает, только если у родителя всего один дочерний элемент (любого типа). Если рядом есть текстовый узел или комментарий — элемент всё равно считается единственным ребёнком-элементом, но :only-child вернёт false, так как учитывает все узлы.
Нюанс

only-child и текстовые узлы:В спецификации DOM текстовые ноды (пробелы, переносы) являются полноценными дочерними узлами. Поэтому <div><p>Текст</p></div> — у div один ребёнок-элемент, но если HTML отформатирован с переносами, у div будет 3 дочерних узла: текст, p, текст. Селектор p:only-child вернёт false. Решение: используйте :only-of-type или минифицируйте HTML.

JavaScript API: children, firstElementChild, querySelectorAll

В JS свойство parent.children возвращает живую HTMLCollection только из элементов-прямых-потомков (игнорирует текстовые ноды). Это быстрее и удобнее childNodes, который включает всё подряд.

Для поиска по селектору используйте parent.querySelectorAll(':scope > .item'). Псевдокласс :scope завязывает селектор на конкретный элемент, а не на документ. Альтернатива — parent.querySelectorAll('> .item') (работает в современных браузерах).

const list = document.querySelector('.nav');

const directItems = list.children; // HTMLCollection только прямых li

const firstItem = list.firstElementChild; // первый прямой потомок-элемент

const lastItem = list.lastElementChild;

const allDirect = list.querySelectorAll(':scope > li'); // NodeList

💡

В цикле по children лучше конвертировать в массив: [...parent.children].forEach(el => ...). HTMLCollection живая — если вы удаляете элементы внутри цикла, индексы сдвигаются и можно пропустить элементы.

Частые ошибки и паттерны анти-паттерны

Ошибка №1: писать .parent > * для «всех прямых детей». Это работает, но снижает читаемость и может зацепить служебные элементы (например, <script> или <style> внутри компонента). Лучше явно перечислить теги или классы.

Ошибка №2: ожидать, что article > p найдёт параграф, обёрнутый в <div> для стилизации. Верстка часто добавляет обёртки, ломая прямую вложенность. Решение — либо менять селектор на article p, либо использовать :where() для игнорирования обёрток: article > :where(div, section) > p.

⚠️ Внимание: комбинатор > не проходит через Shadow DOM границы. Если компонент использует теневой DOM, стили снаружи не попадут к его прямым потомкам внутри тени. Нужно либо стилизовать изнутри, либо использовать CSS-переменные (custom properties), которые пробивают границу.
💡

Прямой комбинатор > — главный инструмент инкапсуляции стилей в компонентной архитектуре. Он гарантирует, что изменения внутренней структуры потомков не сломают верстку родителя и наоборот.

Продвинутые техники: :where(), :has() и будущие селекторы

С приходом :has() (поддерживается во всех современных браузерах с 2023 года) появилась возможность стилизовать родителя в зависимости от прямых потомков. Например, ul:has(> li.active) подсветит весь список, если у него есть активный прямой потомок.

Функция :where() обнуляет специфичность, что удобно для утилитарных селекторов: :where(article, section) > :where(h1, h2, h3) выберет заголовки первого уровня вложенности с нулевой специфичностью, легко переопределяемой проектными стилями.

В черновике Selectors Level 4 обсуждается комбинатор || для выбора ячеек таблицы по колонкам, но он касается другой оси. Для прямых потомков основной инструмент остаётся >, дополненный псевдоклассами структурных позиций.

📊 Какой селектор потомков вы используете чаще всего?
Прямой комбинатор > (parent > child)
Потомок через пробел (parent child)
Смежный + / родственный ~
Псевдоклассы :first-child, :nth-child
Микс всех вышеперечисленных

Чек-лист: правильная работа с прямыми потомками

  • ✅ Используйте > для изоляции стилей компонентов (БЭМ, CSS Modules)
  • ✅ Проверяйте реальную вложенность в DevTools (Elements → Computed → наведение на селектор)
  • ✅ В JS предпочитайте children и firstElementChild вместо childNodes
  • ✅ Комбинируйте с :where() для низкой специфичности утилит
  • ✅ Учитывайте Shadow DOM границы при работе с веб-компонентами

FAQ: частые вопросы про прямых потомков

В чём разница между child и descendant в терминологии CSS?

Child (ребёнок / прямой потомок) — элемент, вложенный ровно на один уровень глубже родителя. Descendant (потомок) — любой элемент внутри родителя на любой глубине: дети, внуки, правнуки и так далее. Селектор parent > child ловит только детей, parent descendant — всех потомков.

Почему p:first-child не работает, если перед p стоит h2?

Псевдокласс :first-child проверяет, является ли элемент первым среди всех детей родителя, а не первым среди параграфов. Если перед <p> есть <h2>, то параграф уже не первый ребёнок. Используйте p:first-of-type для выбора первого параграфа по типу.

Как выбрать все прямые потомки любого типа?

Селектор parent > * выберет все элементы-прямых-потомков. Но будьте осторожны: он зацепит абсолютно всё — включая служебные теги, если они попали в разметку. Часто лучше перечислить нужные теги: parent > div, parent > span, parent > p.

Работает ли > через Shadow DOM границу?

Нет. CSS-селекторы из Light DOM не проникают внутрь Shadow DOM. Прямые потомки внутри теневого дерева невидимы для внешних селекторов с комбинатором >. Стилизуйте их изнутри компонента или используйте CSS-переменные (custom properties), которые наследуются сквозь границу.

Какая специфичность у селектора .menu > li.active?

Складывается из частей: класс .menu (0-1-0), комбинатор не добавляет веса, тег li (0-0-1), класс .active (0-1-0). Итого: 0-2-1. Если нужно снизить вес, оберните классы в :where(): :where(.menu) > :where(li.active) даст 0-0-1.

💡

Понимание разницы между прямыми потомками и всеми потомками — базовый навык для предсказуемой вёрстки. Комбинатор > даёт контроль, производительность и инкапсуляцию, необходимые в современной компонентной разработке.