Псевдоэлемент ::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, не замещаемый ли это элемент и не перебило ли правило другим селектором с большей специфичностью.