Псевдокласс :nth-of-type() – структурный селектор из спецификации Selectors Level 3, ближайший родственник :nth-child(). Разница в том, какой список нумерует браузер: :nth-child() считает всех детей родителя подряд, а :nth-of-type() ведёт отдельную нумерацию для каждого имени тега. Отсюда и «type» в названии – речь о типе элемента, то есть теге, а не о классе или атрибуте. В смешанной разметке, где абзацы перемешаны с заголовками и картинками, именно он даёт предсказуемый счёт.
Как это работает
Для каждого ребёнка браузер берёт имя его тега, собирает одноимённых соседей и нумерует их сверху вниз начиная с 1. Аргументы стандартные для nth-семейства: число, odd/even или формула An+B. Синтаксиса of S у :nth-of-type() нет – он определён только для :nth-child() и :nth-last-child(). Классический пример из документации MDN – контейнер со смешанными тегами:
<div>
<div>Этот элемент не считается.</div>
<p>1-й абзац.</p>
<p class="fancy">2-й абзац.</p>
<div>Этот элемент не считается.</div>
<p class="fancy">3-й абзац.</p>
<p>4-й абзац.</p>
</div>p:nth-of-type(2n + 1) { color: red; } /* нечётные абзацы: 1-й и 3-й */
p:nth-of-type(2n) { color: blue; } /* чётные абзацы: 2-й и 4-й */
p:nth-of-type(1) { font-weight: bold; } /* первый абзац */Оба div при подсчёте абзацев игнорируются. Сравните с :nth-child(): здесь p:nth-child(2) выберет первый абзац, потому что он второй ребёнок родителя. Классический «обман» выглядит так: span:nth-child(3) исправно красит третий span, пока вёрстка однородна, но стоит вставить в контейнер div или ссылку – и третьим ребёнком становится другой элемент, селектор молча промахивается. Такие баги всплывают после безобидной правки контента и потому особенно неприятны: стили «ломаются сами», хотя CSS никто не трогал. p:nth-of-type(3) в той же ситуации продолжит указывать на третий абзац.
p.fancy:nth-of-type(2): класс проверяется после подсчёта
Частая ошибка – ожидание, что p.fancy:nth-of-type(2) выберет «второй абзац с классом fancy». На деле подсчёт идёт только по имени тега, а составной селектор работает как пост-фильтр: сначала браузер находит второй p среди всех p, потом проверяет, есть ли у него класс fancy. В разметке выше p.fancy:nth-of-type(2n+1) выберет только 3-й абзац: он нечётный по счёту абзацев и при этом с классом. «Второй элемент с классом» через :nth-of-type() выразить нельзя – MDN прямо пишет, что «nth-of-class» этим селектором не делается. Эту задачу закрывает современный синтаксис of S: p:nth-child(2 of .fancy).
nth-child, nth-of-type и of S – что выбрать
| Селектор | Кого нумерует | Типовая задача | Поддержка |
|---|---|---|---|
| :nth-child(2) | Всех детей родителя подряд | Позиция в общем потоке: зебра таблицы, первые три карточки | Все браузеры с 2015, IE9+ |
| :nth-of-type(2) | Только соседей с тем же тегом | Счёт по тегу в смешанной разметке: второй абзац статьи | Все браузеры с 2015, IE9+ |
| :nth-child(2 of .fancy) | Только элементы, подходящие под селектор S | «nth-of-class»: второй элемент с нужным классом | Chrome/Edge 111 (2023), Firefox 113 (2023), Safari 9 |
Зачем это нужно
Польза раскрывается в смешанном контенте, где классы расставить нельзя или неудобно:
- Типографика статей – каждый второй абзац, чередование фонов, буквица в первом абзаце: счёт не сбивается на заголовках и врезках.
- Списки определений – dd:nth-of-type(even) подсветит чётные описания, игнорируя dt при подсчёте.
- Вёрстка под CMS – когда HTML генерирует визуальный редактор или компонент и добавить классы к элементам невозможно.
Побочный бонус – чистота разметки: чем меньше служебных классов вроде first или odd, тем проще HTML, который читают и краулеры, и скринридеры. Логика чередования живёт в CSS, а контент-менеджер в админке не обязан помнить про специальные классы при каждой правке текста.
У псевдокласса целое семейство «по типу»: :first-of-type, :last-of-type, :only-of-type и :nth-last-of-type() – счёт по тегу с конца, по аналогии с :nth-last-child(). Когда разметка однородна – например, список из одних li, – :nth-child() и :nth-of-type() дают одинаковый результат, и лучше выбрать более привычный :nth-child(). А когда считать нужно по классу или атрибуту, а не по тегу, честнее сразу взять of S, чем подгонять пост-фильтры.
Пример
Типовая задача контент-хаба: текст статьи приходит из админки Битрикса единым HTML – заголовки, абзацы, таблицы и цитаты вперемешку, классов у абзацев нет. Нужно выделить первый абзац как лид и задать отступы остальным:
.article__body p:first-of-type {
font-size: 1.25em;
}
.article__body p:nth-of-type(n + 2) {
margin-top: 1em;
}Здесь :first-of-type – то же, что :nth-of-type(1): он найдёт первый абзац, даже если перед ним стоят h2 или figure, тогда как p:nth-child(1) в такой разметке не выбрал бы ничего. Специфичность :nth-of-type() равна одному классу, поэтому правило легко переопределяется точечными стилями – подробнее в статье о специфичности CSS. Поддержка: по данным MDN, статус Baseline Widely Available, все браузеры с июля 2015 года, исторически IE9+ (Selectors Level 3) – префиксы и полифилы в 2026 году не нужны.