Three Approaches to the “&” (ampersand) Selector in CSS

In CSS nesting, the & (ampersand symbol) selector adds style rules based on the relation between nested selectors. For example, a pseudo-class (:hover) nested inside a type selector (div) becomes a compound selector (div:hover) when the nested pseudo-class is prefixed with &.

div {
  &:hover {
    background: green;
  }
}

/*
The above code is equivalent to:
div:hover {
  background: green;
}
*/Code language: CSS (css)

A use-case for the & selector is to combine it with the :has() pseudo-class to select and style elements based on the child elements they contain. In the following example, a label is styled for when the checkbox within it is checked.

<label>
  <input type="checkbox">
  Allow access to all files
</label>
Code language: HTML, XML (xml)
label {
  /* etc. */
  &:has(:checked) {
    border: 1px solid lime;
  }
}

/*
  The above code is equivalent to:

  label:has(:checked) {
    border: 1px solid lime;
  }
*/Code language: CSS (css)

In a way, the & symbol is a placeholder for the outer level selector in the nesting hierarchy. This enables flexible combinations of selectors customized to suit our code’s modular organization preferences. In this article, we’ll see three kinds of modular possibilities with the & selector in native CSS.

1) Linked Class Names

Starting with the easiest approach, the & selector can be used to combine class names. Elements often have multiple class names to group and style them together. Sometimes, grouping is within a module, and other times style rules can intersect between modules due to shared class names.

By using the & selector to concatenate class names, style rules for elements within a module can be arranged together based on their shared class names.

In the example below, the three card modules, grouped under the class name cards, share most style rules, such as dimensions. However, they have different background images to reflect their content, defined separately within the .cards ruleset by concatenating the & selector with the exclusive class names of each card.

<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>Code language: HTML, XML (xml)
.cards {
  background: center/cover var(--bg);

  &.trek {
    --bg: url("trek.jpg");
  }
  &.wildlife {
    --bg: url("wildlife.jpg");
  }
  &.stargaze {
    --bg: url("stargaze.jpg");
  }
}Code language: CSS (css)

Class names can also be connected using the attribute selector:

<div class="element-one">text one</div>
<div class="element-two">text two</div>Code language: HTML, XML (xml)
[class|="element"] {
  &[class$="one"] { color: green; }
  &[class$="two"] { color: blue; }
}Code language: CSS (css)

Another example is:

<div class="element_one">text one</div>
<div class="element_two">text two</div>Code language: HTML, XML (xml)
[class^="element"] {
  &[class$="one"] { color: green; }
  &[class$="two"] { color: blue; }
}Code language: CSS (css)

2) Parent and Previous Element Selectors

The conventional method of organizing nested style rulesets involves including the rulesets of child elements within the ruleset of their parent elements.

<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>Code language: HTML, XML (xml)
.error {
  color: red;
  a {
    color: navy;
  }
}Code language: CSS (css)

However, the opposite is also possible due to the & selector: nesting a parent element’s style rules within its child element’s ruleset. This can be beneficial when it’s easier to organize an element’s style rules by its purpose rather than its position in a layout.

For instance, styling can be static or dynamic. Dynamic styling happens when user interactions, or scripts, trigger the application of a selector’s ruleset to an element on a page. In such cases, it’s convenient to categorize rulesets into dynamic and static.

In the following example, all rules related to the appearance of the agreement modules upon page load, such as layout, dimensions, and colors, are included in the .agreements ruleset. However, the style rules that modify the appearance of the agreement modules when their checkboxes are checked, i.e when user interaction occurs, are placed within the nesting selector .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>Code language: HTML, XML (xml)
.agreements {
  /* etc. */
  &.terms {  --color: rgb(45 94 227);  }
  &.privacy { --color: rgb(231 51 35); }
  &.diagnostics { --color: rgb(59 135 71); }
  /* etc. */
}

.isAgreed:checked { 
  /* checkbox's background color change */
  background: var(--color);
  /* checkbox's border color change */
  border-color: var(--color);
  /* checkbox shows a checkmark */
  &::before { content: '\2713'; /* checkmark (✓) */ }
  
  /* Agreement section's (checkbox's parent) border color change */
  .agreements:has(&) { 
    /* same as .agreements:has(.isAgreed:checked) */
    border-color: var(--color); 
  }
}Code language: CSS (css)

In the above demo, a parent element selection is shown as example, but the same logic applies for previous element selections as well.

<p>some text</p>
<input type="checkbox"/>Code language: HTML, XML (xml)
/* When the checkbox is checked */
:checked {
  /* 
    rules to style the checkbox
  */

  /* style for <p> when the checkbox is checked */
  p:has(+&) { 
    /* same as p:has(+:checked) */
    color: blue;    
  }
}Code language: CSS (css)

3) Recurring Selectors

With IDs, class names, semantic markup, and so forth, we rarely need to repeat selectors within compound selectors to achieve specificity. However, repeating selectors are still useful, particularly when the same type of elements are styled similarly but with slight adjustments based on their positions in the layout and among themselves.

A good example of this is how paragraphs are typically spaced in an article. There’s the spacing between each paragraph, and the spacing between the paragraphs and another kind of element, such as an image, that’s inserted between them.

<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>Code language: HTML, XML (xml)
article {
  /* etc. */
  p {
    margin: 0;
    
    /* <p> that's after/below an element that's not <p> */
    *:not(&) + & { 
      margin-top: 30px; 
    }
    
    /* <p> that's before/above an element that's not <p> */
    &:not(:has(+&)) { 
      margin-bottom: 30px; 
    } 
    
    /* <p> that's after/below another <p> */
    & + & {
      margin-top: 12px; 
    }
  }
  /* etc. */
}Code language: CSS (css)

In the above example, the gaps between paragraphs are small compared to the larger gaps between a paragraph and a non-paragraph element.

The selectors can be explained like:

  1. *:not(p) + p — The paragraph below a non-paragraph element has a larger top margin
  2. p:not(:has(+p)) — The paragraph above a non-paragraph element has a larger bottom margin
  3. p + p — The paragraph below another paragraph has a smaller top margin

Besides its flexibility, another compelling reason to use the & selector in organizing our code is that it lacks its own specificity. This means we can rely on the specificity of the usual selectors and the nesting hierarchy to apply the rules as intended.

If you’re looking to level up your skills in CSS nesting, check out Kevin Powell’s new course Build a Website from Scratch and Jen Kramer’s Practical CSS Layouts both of which cover CSS nesting and lots more!

Using & in Vanilla CSS vs. Using & in Frameworks

The & is vanilla CSS always represents the outer level selector, which might not be the case for the & used in CSS frameworks, such as Sass. The & in frameworks could mean the outer level string.

<div class="parent--one">text one</div>
<div class="parent--two">text two</div>Code language: HTML, XML (xml)

In Sass (SCSS), the style could be:

.parent {
  &--one { color: green; }
  &--two { color: blue; }
}Code language: SCSS (scss)

That will not work in vanilla CSS, but it still can be done! A similar ruleset would be:

[class|="parent"] {
  &[class$="--one"] { color: green; }
  &[class$="--two"] { color: blue; }
}Code language: CSS (css)

Note that, in the SCSS code, there is no real .parent selector, as in no element on page matches it, whereas in CSS, [class|="parent"] will match an element. If we add a style rule to the outer level ruleset in both of those code snippets, the SCSS will fail to find an element to apply the style rule, whereas the CSS will apply the style to both the elements that has the class name starting with parent.

.parent {
  font-weight: bold; /* this won’t work, as it doesn't match anything */
  &--one { color: green; }
  &--two { color: blue; }
}Code language: SCSS (scss)
[class|="parent"] {
  font-weight: bold; /* works */
  &[class$="one"] { color: green; }
  &[class$="two"] { color: blue; }
}Code language: CSS (css)

Hence, the downside of an & that represents a selector-syntax string rather than a viable selector is that it might mislead us into thinking elements matching the perceived selector exist when they don’t, something that we don’t have to worry with the native & selector.

When using a nested style rule, one must be able to refer to the elements matched by the parent rule; that is, after all, the entire point of nesting. To accomplish that, this specification defines a new selector, the nesting selector, written as & (U+0026 AMPERSAND).

When used in the selector of a nested style rule, the nesting selector represents the elements matched by the parent rule. When used in any other context, it represents the same elements as :scope in that context (unless otherwise defined).

— CSS Nesting Module 1, W3C

On the other hand, we can combine strings more freely to produce the selectors we want using the & in frameworks. Which is useful when class names are extensively relied on for modularity.

Either way, grouping style rulesets is crucial for enhancing code readability, maintainability, and to provide a desired order of use among conflicting style rules. The & selector in native CSS can help with that, as well as in specifying selection criteria that might otherwise be challenging to define.

Further Reading

Wanna learn CSS from a course?

Leave a Reply

Your email address will not be published. Required fields are marked *

Did you know?

Frontend Masters Donates to open source projects. $363,806 contributed to date.