Псевдоэлемент ::before

::before – CSS-псевдоэлемент, который создаёт «виртуального» первого ребёнка элемента и вставляет в него сгенерированный контент через обязательное свойство content. Без content не отображается; на img и input не работает.

4 минуты чтения

Псевдоэлемент ::before работает в паре с ::after и описан в модуле спецификации CSS Pseudo-Elements Level 4 (сами селекторы и псевдоклассы описывает Selectors Level 4). Ключевой момент, который часто понимают неверно: контент вставляется не «перед элементом», а внутрь него – ::before становится первым дочерним элементом выбранного узла, ::after – последним. В DOM этого ребёнка нет, поэтому такой контент называют сгенерированным (generated content): его создаёт браузер по инструкции из CSS, а не разметка. Поддержка давно не вопрос: по Baseline псевдоэлемент widely available – работает во всех браузерах с июля 2015 года.

Как это работает

Синтаксис: селектор, двойное двоеточие, имя псевдоэлемента и обязательное свойство content в блоке правил:

a.external::before {
  content: "→ ";
  color: #888;
}

Без content псевдоэлемент не отрисуется вовсе: значение по умолчанию normal для ::before вычисляется в none, и он ведёт себя как display: none. Забытый content – самая частая причина жалобы «::before не работает». Для чисто декоративного блока задают пустую строку: content: "".

Свойство content принимает не только текст:

  • строка и Unicode-символы – маркеры, стрелки, типографские знаки;
  • url() – изображение;
  • counter() и counters() – автоматическая нумерация;
  • attr() – значение атрибута элемента, например href или data-атрибута;
  • open-quote и close-quote – языкозависимые кавычки;
  • комбинации значенийcontent: " (" attr(href) ")".

По умолчанию ::before создаёт строчный (inline) бокс: width и height не сработают, пока не задан display: block или inline-block. В остальном псевдоэлемент стилизуется как обычный элемент, наследует стили родителя и поддерживает transitions и animations. В селекторе он всегда стоит в конце и свободно комбинируется с псевдоклассами: li:hover::before, li:not(.done)::before.

:before и ::before – в чём разница

Одиночное двоеточие – синтаксис CSS2, двойное ввели в CSS3, чтобы псевдоэлементы синтаксически отличались от псевдоклассов. Для четырёх «старых» псевдоэлементов – :before, :after, :first-line, :first-letter – браузеры поддерживают обе записи: :before работает даже в IE8, ::before – начиная с IE9. В новом коде используют только двойное двоеточие; одиночное встречается в легаси-стилях и ошибкой не считается.

Псевдоэлемент и псевдокласс – в чём разница

Псевдокласс и псевдоэлемент: сравнение
КритерийПсевдоклассПсевдоэлемент
Что делаетВыбирает существующий элемент в определённом состоянииСтилизует или создаёт часть, которой нет в дереве документа
СинтаксисОдно двоеточиеДва двоеточия (у четырёх старых допустимо одно)
Примеры:hover, :focus, :first-child, :nth-child()::before, ::after, ::first-line, ::marker
Место в селектореЛюбая часть составного селектораТолько в конце: p::first-line > * – невалидно
КомбинацияМожет стоять перед псевдоэлементом: p:hover::beforeСтавится после всех псевдоклассов

Зачем это нужно

Главная ценность – декор и служебный контент без правки HTML. Разметка остаётся чистой и семантичной, а повторяющиеся визуальные детали живут в одном месте – в CSS. Типовые применения:

/* типографские кавычки */
q::before { content: "«"; }
q::after  { content: "»"; }

/* нумерация заголовков */
h2 { counter-increment: section; }
h2::before { content: counter(section) ". "; }

/* URL внешних ссылок при печати */
a[href^="https://"]::after { content: " (" attr(href) ")"; }

/* clearfix (парный ::after) */
.clearfix::after { content: ""; display: table; clear: both; }

Пустой content: "" превращает псевдоэлемент в универсальный декоративный блок: из ::before и ::after делают ленты, оверлеи, кастомные чекбоксы и галочки. Фактически это два «бесплатных» слоя у каждого элемента, которые избавляют вёрстку от лишних пустых div.

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

Ограничения

::before и ::after не применяются к замещаемым (replaced) элементам – img, input и другим, чьё содержимое определяется внешним ресурсом (файлом изображения, виджетом браузера), а не CSS документа. Вставить HTML-разметку через content тоже нельзя: среди его значений только текст, изображения, счётчики и attr(), строки не парсятся как HTML.

Доступность: читают ли скринридеры content

Распространённый миф из старых статей – «скринридеры не видят сгенерированный контент». Актуальная позиция MDN другая: текстовый content попадает в дерево доступности и читается современными скринридерами. Для изображений и символов спецификация предусматривает синтаксис альтернативного текста через слеш:

a::before {
  content: url("favicon.ico") / "MOZILLA: ";
}
.fav::before {
  content: url("star.svg") / "в избранном";
}

Поддержка слеш-синтаксиса пока не повсеместная, поэтому правило прежнее: важную информацию в content не помещать. Есть и вторая причина: сгенерированного контента нет в HTML, и парсеры, работающие с исходной разметкой, его не получат. Держите в content декор, а смысл – в разметке; о принципах доступной вёрстки – в термине a11y.

Пример

Классическая задача на контентных сайтах, в том числе на Битрикс-проектах: редактор вставляет в визуальном редакторе обычные списки ul, а дизайн требует фирменные маркеры. Править HTML каждой статьи – тупик; ::before решает задачу на уровне шаблона:

.article ul { list-style: none; }
.article li {
  position: relative;
  padding-left: 1.2em;
}
.article li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: currentColor;
}

Разметка не меняется: контент-менеджер работает как привык, а маркеры единообразны на всём сайте и правятся в одной точке CSS. Если ::before «не появился», проверяйте по порядку: задан ли content, не замещаемый ли это элемент и не перебило ли правило другим селектором с большей специфичностью.

Частые вопросы

Почему ::before не отображается?

Самая частая причина – отсутствует свойство content: без него (или при content: none) псевдоэлемент не рендерится вообще. Для чисто декоративного блока задайте пустую строку content: "". Также проверьте, что элемент не замещаемый: на img и input псевдоэлементы не работают.

В чём разница между :before и ::before?

Одно двоеточие – синтаксис CSS2, двойное ввели в CSS3, чтобы псевдоэлементы синтаксически отличались от псевдоклассов. Браузеры поддерживают обе записи для :before, :after, :first-line и :first-letter: вариант :before работает даже в IE8, ::before – с IE9. В новом коде используйте ::before.

Чем псевдоэлемент отличается от псевдокласса?

Псевдокласс выбирает существующий элемент в определённом состоянии: :hover, :focus, :first-child. Псевдоэлемент стилизует или создаёт часть, которой нет в дереве документа: ::before, ::first-line. Синтаксическая подсказка – одно двоеточие против двух; их можно комбинировать, например p:hover::before.

Почему ::before не работает на img и input?

Это замещаемые (replaced) элементы: их содержимое определяется внешним ресурсом – файлом изображения, виджетом браузера, а не CSS документа, поэтому генерируемый контент к ним не применяется. Практический выход – вешать ::before на обычный элемент-обёртку вокруг них, например span или label.

Видят ли скринридеры содержимое content?

Да, текст из content попадает в дерево доступности и озвучивается современными скринридерами – утверждение «скринридеры не читают content» устарело. Для изображений и символов есть синтаксис альтернативного текста: content: url("star.svg") / "в избранном". Его поддержка пока не повсеместная, поэтому важную информацию в content помещать нельзя.

Можно ли анимировать ::before?

Да, псевдоэлементы поддерживают CSS transitions и animations: цвет, размеры, трансформации анимируются как у обычных элементов. Исключение – само свойство content: по MDN оно меняется дискретно, без плавной интерполяции значений.

Можно ли вставить HTML через content?

Нет. Свойство content принимает строки, изображения через url(), счётчики counter() и counters(), значения атрибутов через attr() и кавычки open-quote/close-quote – разметки среди значений нет. Строка вставляется как обычный текст, браузер не парсит её как HTML.

Материалы по теме

Валентина Меланина

Нужна консультация?

Разберу ваш сайт и покажу точки роста

Если хотите понять, как этот термин применить к вашему проекту — начнём с аудита.