Три подхода к селектору &
(амперсанд) в CSS
&
— мощное дополнение к CSS, позволяющее создавать селекторы без повторений и способствующее улучшению организованности и понимания кода.В CSS селектор &
(символ амперсанда) добавляет правила стиля, основанные на соотношении между вложенными селекторами. Например, псевдокласс (:hover
), вложенный в селектор типа (div
), становится составным селектором (div:hover
), если вложенному псевдоклассу присвоен префикс &
.
div {
&:hover {
background: green;
}
}
/*
Приведённый выше код эквивалентен:
div:hover {
background: green;
}
*/
Селектор &
можно использовать в сочетании с псевдоклассом :has()
для выбора и стилизации элементов на основе дочерних элементов, которые они содержат. В следующем примере label
стилизуется под отмеченный внутри него чекбокс.
<label>
<input type="checkbox">
Allow access to all files
</label>
label {
/* и т.д. */
&:has(:checked) {
border: 1px solid lime;
}
}
/*
Приведённый выше код эквивалентен:
label:has(:checked) {
border: 1px solid lime;
}
*/
В некотором смысле символ &
— это заменитель селектора внешнего уровня в иерархии вложенности. Это позволяет создавать гибкие комбинации селекторов в соответствии с предпочтениями модульной организации кода. В статье мы рассмотрим три вида модульных возможностей с использованием селектора &
в обычном CSS.
Связанные имена классов
Начнём с самого простого подхода: селектор &
можно использовать для объединения имён классов. Элементы часто содержат несколько имён классов для их группировки и стилизации. Иногда группировка происходит внутри модуля, а иногда правила стиля могут пересекаться между модулями из-за общих имён классов.
В приведённом ниже примере три модуля карточек, объединённых в класс cards
, имеют общие правила стиля, такие как размеры. Однако у них разные фоновые изображения, отражающие их содержание, определяемое отдельно в наборе правил .cards
путём конкатенации селектора &
с эксклюзивными именами классов каждой карточки.
<div class="cards trek">
<p>Trekking</p>
</div>
<div class="cards wildlife">
<p>Wildlife spotting</p>
</div>
<div class="cards stargaze">
<p>Stargazing camp</p>
</div>
.cards {
background: center/cover var(--bg);
&.trek {
--bg: url("trek.jpg");
}
&.wildlife {
--bg: url("wildlife.jpg");
}
&.stargaze {
--bg: url("stargaze.jpg");
}
}
Имена классов также можно подключать с помощью селектора атрибутов:
<div class="element-one">text one</div>
<div class="element-two">text two</div>
[class|="element"] {
&[class$="one"] { color: green; }
&[class$="two"] { color: blue; }
}
Ещё один пример:
<div class="element_one">text one</div>
<div class="element_two">text two</div>
[class^="element"] {
&[class$="one"] { color: green; }
&[class$="two"] { color: blue; }
}
Селекторы родительского и предыдущего элементов
Традиционный метод организации вложенных наборов правил стиля предполагает включение наборов правил дочерних элементов в набор правил их родительских элементов.
<p class="error">
Server down. Try again after thirty minutes.
If you still can't access the site,
<a href="/errorReport">submit us a report</a>.
We'll resolve the issue within 24hrs.
</p>
.error {
color: red;
a {
color: navy;
}
}
Однако благодаря селектору &
возможно и обратное: вложение правил стиля родительского элемента в набор правил его дочернего элемента. Это может быть удобно, когда проще организовать стилевые правила элемента по его назначению, а не по его положению в макете.
Например, стилизация может быть статической или динамической. Динамическая стилизация происходит, когда пользовательские действия или скрипты вызывают применение набора правил селектора к элементу на странице. В таких случаях удобно делить наборы правил на динамические и статические.
В следующем примере все правила, связанные с внешним видом модулей соглашения при загрузке страницы, такие как макет, размеры и цвета, включены в набор правил .agreements
. Однако правила стиля, изменяющие внешний вид модулей соглашения при установке чекбоксов, т. е. при взаимодействии с пользователем, помещаются во вложенный селектор .isAgreed:checked
.
<article class="agreements terms">
<header><!-- ... --></header>
<section>
<input class="isAgreed" type="checkbox" />
<!-- ... -->
</section>
<footer><! -- ... --></footer>
</article>
<article class="agreements privacy">
<header><!-- ... --></header>
<section>
<input class="isAgreed" type="checkbox" />
<!-- ... -->
</section>
<footer><! -- ... --></footer>
</article>
<article class="agreements diagnostics">
<header><!-- ... --></header>
<section>
<input class="isAgreed" type="checkbox" />
<!-- ... -->
</section>
<footer><!-- ... --></footer>
</article>
.agreements {
/* и т.д. */
&.terms { --color: rgb(45 94 227); }
&.privacy { --color: rgb(231 51 35); }
&.diagnostics { --color: rgb(59 135 71); }
/* и т.д. */
}
.isAgreed:checked {
/* изменение цвета фона чекбокса */
background: var(--color);
/* изменение цвета границы чекбокса */
border-color: var(--color);
/* чекбокс отображает отметку */
&::before { content: '\2713'; /* отметка (✓) */ }
/* Изменение цвета границы раздела соглашения (родителя чекбокса) */
.agreements:has(&) {
/* то же самое, что и .agreements:has(.isAgreed:checked)*/
border-color: var(--color);
}
}
В приведённом выше примере показано выделение родительского элемента, но та же логика применима и к выделению предыдущих элементов.
<p>some text</p>
<input type="checkbox"/>
/* Когда чекбокс отмечен */
:checked {
/*
правила стилизации чекбокса
*/
/* стиль для <p>, когда чекбокс отмечен */
p:has(+&) {
/* то же, что и p:has(+:checked) */
color: blue;
}
}
Рекуррентные селекторы
Благодаря идентификаторам, именам классов, семантической разметке и т. д. нам редко приходится повторять селекторы внутри составных селекторов, чтобы добиться специфичности. Однако повторяющиеся селекторы всё равно полезны, особенно когда элементы одного типа стилизуются одинаково, но с небольшими корректировками в зависимости от их положения в макете и между собой.
Хорошим примером этого является расположение абзацев в статье. Есть интервал между каждым абзацем, а также интервал между абзацами и другим элементом, например, изображением, помещённым между ними.
<article>
<header><!--...--></header>
<p><!--...--></p>
<figure><!--...--></figure>
<p><!--...--></p>
<p><!--...--></p>
<p><!--...--></p>
<blockquote><!--...--></blockquote>
<p><!--...--></p>
<p><!--...--></p>
<figure><!--...--></figure>
<p><!--...--></p>
<footer><!--...--></footer>
</article>
article {
/* и т.д. */
p {
margin: 0;
/* <p> находящийся после/ниже элемента, не являющегося <p> */
*:not(&) + & {
margin-top: 30px;
}
/* <p> находящийся перед/над элементом, не являющимся <p>. */
&:not(:has(+&)) {
margin-bottom: 30px;
}
/* <p> что расположенное после/под другим <p> */
& + & {
margin-top: 12px;
}
}
/* и т.д. */
}
В приведённом выше примере интервалы между параграфами малы по сравнению с большими интервалами между параграфом и элементом, не являющимся параграфом.
Объяснить работу селекторов можно следующим образом:
*:not(p) + p
— Параграф под элементом, не являющимся параграфом, имеет больший верхнийmargin
p:not(:has(+p))
— Параграф над элементом, не являющимся параграфом, имеет больший нижнийmargin
p + p
— Параграф под другим параграфом имеет меньший верхнийmargin
Помимо гибкости, ещё одна веская причина использования селектора & при организации кода заключается в том, что он не имеет собственной специфичности. Это означает, что можно положиться на специфичность обычных селекторов и иерархию вложенности, чтобы применить правила по назначению.
Использование &
в ванильном CSS vs. использование &
в фреймворках
&
в ванильном CSS всегда представляет селектор внешнего уровня, что может не совпадать с &
, используемым во фреймворках CSS, таких, как Sass. &
во фреймворках может означать строку внешнего уровня.
<div class="parent--one">text one</div>
<div class="parent--two">text two</div>
В Sass (SCSS) стиль может быть следующим:
.parent {
&--one { color: green; }
&--two { color: blue; }
}
Это не будет работать в ванильном CSS, но это всё равно можно сделать! Аналогичный набор правил будет выглядеть так:
[class|="parent"] {
&[class$="--one"] { color: green; }
&[class$="--two"] { color: blue; }
}
Обратите внимание, что в коде SCSS нет реального селектора .parent
, так как ему не соответствует ни один элемент на странице, в то время как в CSS [class|="parent"]
будет соответствовать элементу. Если добавить правило стиля в набор правил внешнего уровня в обоих этих фрагментах кода, SCSS не сможет найти элемент для применения правила стиля, в то время как CSS применит стиль к обоим элементам, имя класса которых начинается с parent
.
.parent {
font-weight: bold; /* это не сработает, так как не соответствует ничему */
&--one { color: green; }
&--two { color: blue; }
}
[class|="parent"] {
font-weight: bold; /* работает */
&[class$="one"] { color: green; }
&[class$="two"] { color: blue; }
}
Таким образом, недостатком &
, представляющего строку синтаксиса селектора, а не действующий селектор, является то, что он может ввести в заблуждение, заставив думать, что элементы, соответствующие предполагаемому селектору, существуют, но это не так, о чем не стоит беспокоиться с нативным селектором &
.
При использовании вложенного правила стиля необходимо иметь возможность ссылаться на элементы, которым соответствует родительское правило; в этом, в конце концов, и заключается смысл вложенности. Для этого в данной спецификации определён новый селектор — селектор вложенности, записываемый как
&
(U+0026 AMPERSAND).При использовании в селекторе вложенного правила стиля, селектор вложенности представляет элементы, соответствующие родительскому правилу. При использовании в любом другом контексте он представляет те же элементы, что и
:scope
в этом контексте (если не определено иное).
С другой стороны, мы можем более свободно комбинировать строки для создания нужных селекторов, используя &
во фреймворках. Это удобно, когда имена классов в значительной степени зависят от модульности.
В любом случае группировка наборов стилевых правил очень важна для повышения читабельности кода, удобства сопровождения и обеспечения желаемого порядка использования конфликтующих стилевых правил. Селектор &
в нативном CSS может помочь в этом, а также в определении критериев выбора, которые иначе было бы сложно определить.