CSS-счетчики (Counters): автоматическая нумерация списков

Забудь про ручную нумерацию: магия CSS-счетчиков

Представь ситуацию: дизайнер приносит макет сложного многоуровневого списка для документации или гайда. Там не просто точки или цифры, а что-то вроде «Глава 1.1», «Шаг 01:», да еще и с кастомными кружочками, градиентами и прочими радостями жизни. Ты смотришь на стандартный тег <ol> и понимаешь, что стилизовать нативный ::marker — это как пытаться припарковать фуру в узком переулке: вроде можно, но больно и неудобно.

Обычно в этот момент рука тянется к JavaScript, чтобы пробежаться циклом по элементам и проставить индексы, или, что еще хуже, ты начинаешь хардкодить цифры прямо в HTML. Остановись. Мы же не в 2010-м. Давай разберем, как заставить браузер делать всю грязную работу за нас с помощью CSS Counters.

Как мы страдали раньше

Раньше у верстальщика было два пути, и оба вели в никуда. Первый — использовать стандартный <ol> и пытаться его «причесать». Но шаг влево, шаг вправо (например, нужно вынести цифру за пределы контейнера или изменить ее формат на «01, 02, 03») — и всё ломалось.

Второй путь — «костыльный». Мы создавали пустые <span> внутри <li> и наполняли их текстом вручную. Стоило заказчику попросить поменять местами второй и третий пункты, как начинался ад с переписыванием всей нумерации. Кстати, если при такой верстке ты запутаешься в иерархии селекторов, советую освежить в памяти статью о том, как правильно работать со специфичностью каскада, чтобы стили счетчиков не конфликтовали с основными правилами.

Как делать правильно в 2026 году

CSS-счетчики — это, по сути, динамические переменные, которые живут в рамках дерева DOM. У них есть три кита: counter-reset (создаем счетчик), counter-increment (накидываем единичку) и функция counter() (выводим результат).

Современный подход позволяет делать нумерацию любой сложности, включая вложенные списки типа «1.1.2», просто используя counters() (во множественном числе). Это работает быстро, не нагружает основной поток JS и идеально подходит для создания семантичной, доступной разметки.


/* 1. Инициализируем счетчик на родительском элементе */
.custom-list {
  list-style: none;
  counter-reset: my-awesome-counter;
  padding-left: 0;
}

/* 2. Итерируем счетчик на каждом элементе списка */
.custom-list li {
  counter-increment: my-awesome-counter;
  display: flex;
  align-items: center;
  margin-bottom: 15px;
}

/* 3. Выводим значение через псевдоэлемент ::before */
.custom-list li::before {
  /* Выводим текущее значение с ведущим нулем */
  content: "Шаг " counter(my-awesome-counter, decimal-leading-zero) ":";
  
  /* Стилизуем как душе угодно */
  background: #ff4e50;
  color: white;
  font-weight: bold;
  padding: 4px 12px;
  border-radius: 20px;
  margin-right: 15px;
  font-size: 0.85rem;
}

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

Частая ошибка новичков

Самый частый «затык» случается, когда забывают про counter-reset. Если ты не обнулишь счетчик на родителе, то при наличии двух одинаковых списков на странице второй список продолжит нумерацию первого. В итоге вместо двух списков «1, 2, 3» ты получишь один длинный «1, 2, 3, 4, 5, 6».

Всегда вешай counter-reset на ближайший общий контейнер элементов, которые нужно пронумеровать. И помни: счетчик инкрементируется только у тех элементов, которые не скрыты через display: none. Если элемент скрыт через visibility: hidden, он все равно учитывается в нумерации. Учитывай это, когда делаешь динамические фильтры или аккордеоны.

🔥 Больше фишек, готовых сниппетов и передовых подходов к CSS мы публикуем в нашем Telegram-канале. Подписывайтесь, чтобы не пропустить!

🚀 Прокачай свой код

Готовые CSS-сниппеты, разбор продвинутых фишек и эксклюзивные материалы — в нашем Telegram-канале.

Подписаться
error: Content is protected !!
Прокрутить вверх