Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Fri, 07 Feb 2025 14:50:07 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 Three Approaches to the “&” (ampersand) Selector in CSS https://frontendmasters.com/blog/three-approaches-to-the-ampersand-selector-in-css/ https://frontendmasters.com/blog/three-approaches-to-the-ampersand-selector-in-css/#respond Fri, 07 Feb 2025 14:50:06 +0000 https://frontendmasters.com/blog/?p=5123 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;
}
*/

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>
label {
  /* etc. */
  &:has(:checked) {
    border: 1px solid lime;
  }
}

/*
  The above code is equivalent to:

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

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>
.cards {
  background: center/cover var(--bg);

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

Class names can also be connected using the attribute selector:

<div class="element-one">text one</div>
<div class="element-two">text two</div>
[class|="element"] {
  &[class$="one"] { color: green; }
  &[class$="two"] { color: blue; }
}

Another example is:

<div class="element_one">text one</div>
<div class="element_two">text two</div>
[class^="element"] {
  &[class$="one"] { color: green; }
  &[class$="two"] { color: blue; }
}

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>
.error {
  color: red;
  a {
    color: navy;
  }
}

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>
.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); 
  }
}

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"/>
/* 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;    
  }
}

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>
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. */
}

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>

In Sass (SCSS), the style could be:

.parent {
  &--one { color: green; }
  &--two { color: blue; }
}

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; }
}

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; }
}
[class|="parent"] {
  font-weight: bold; /* works */
  &[class$="one"] { color: green; }
  &[class$="two"] { color: blue; }
}

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

]]>
https://frontendmasters.com/blog/three-approaches-to-the-ampersand-selector-in-css/feed/ 0 5123
Speed up Sass https://frontendmasters.com/blog/speed-up-sass/ https://frontendmasters.com/blog/speed-up-sass/#respond Fri, 25 Oct 2024 15:15:05 +0000 https://frontendmasters.com/blog/?p=4264 Are you using Sass as part of your build process? If you’re using Vite or webpack and importing SCSS files in lots of different components, be aware you should probably switch to sass-embedded instead of sass, as it can, as James Stuckey Weber wrote, “allows you to reuse a single instance of Sass for multiple compilations” instead of Sass spinning up and down for every single import.

]]>
https://frontendmasters.com/blog/speed-up-sass/feed/ 0 4264
Creating Flower Shapes using CSS Mask & Trigonometric Functions https://frontendmasters.com/blog/creating-flower-shapes-using-css-mask-trigonometric-functions/ https://frontendmasters.com/blog/creating-flower-shapes-using-css-mask-trigonometric-functions/#respond Thu, 29 Feb 2024 15:04:12 +0000 https://frontendmasters.com/blog/?p=1021 Creating unusual shapes is always a fun exercise and a good way to practice your CSS skills. One might argue that SVG is better for this job, but nowadays we have a lot of new CSS tricks that allow us to create shapes with a clean and optimized code. Through this two-article series, we will explore what can be done with CSS nowadays.

Article Series

In this article, we are going to create flower-like shapes. We are going to rely on modern features like mask, trigonometric functions, CSS variables, etc.

four flower-like shapes with purple to pink gradients. two of them have petals and two of them have spikes. two of them are filled and two of them are outlined.

Before we start, you can take a look at my online generator for flower shapes to get an overview of what we are building here. You can easily define your settings and get the CSS code in no time. Some of the code we will be writing can be complex so it’s always good to have a generator to make our life easy. That said I invite you to keep reading to understand the logic behind the code you are copying and be able to tweak it if needed. 

The Geometry of A Flower Shape

The structure of the flower shape can be seen as small circles placed around a bigger one. 

Consider that the small circles touch each other without overlapping each other. This will make the calculation a bit challenging as we will need accurate formulas. Turns out this is a perfect use case for trigonometric functions in CSS.

The shape can be controlled using 2 variables; the number of small circles (N) and their radius (R). From there we can identify the size of the whole shape and the radius of the big circle.

Here is a figure to illustrate some of the values and from where we can extract the different formulas.

geometry of the circles showing the radius comparisons.

I won’t start a boring geometry course so I will jump straight to the formulas we need to use. The size of the element is equal to

2 * (X + R) = 2 * (R/sin(180deg/N) + R) = 2 * R * (1 + 1/sin(180deg/N))

and the radius of the big circle is equal to

C  = R/tan(180deg/N)

We have all that we need to start writing some code.

Coding The Shape

The main challenge is to rely on a single element. We are not going to consider a complex HTML structure where each circle is a different element. Instead, we will only use one element (and no pseudo-elements either!)

I mentioned mask, we’ll be using that, and gradients will allow us to do the shape drawing we want to do. Since it’s all about circles we are going to use radial-gradient(). We will also use a bit of Sass (for the looping feature) to control the number of circles. The number of gradients will depend on the number of circles and with Sass we can write a loop to generate the needed gradients.

Let’s start by setting the different variables and the shape size:

$n: 12; /* the number of circles/petals */

.flower {
  --r: 30px; /* the radius of the small circles */
  width: calc(2*var(--r)*(1 + 1/sin(180deg/#{$n})));
  aspect-ratio: 1;
  mask: radial-gradient(#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0);
  background: /* you background coloration */
}

Nothing fancy so far, we simply translated the previous formulas using code. I also added the big circle so all that we are missing is the small ones. The Sass code to generate them will look like this:

$m: (); /* empty variable */
@for $i from 1 through ($n) { /* loop through the number of circles*/
  $m: append($m, 
    radial-gradient(50% 50%,#000 100%,#0000) no-repeat
    x y / calc(2*var(--r)) calc(2*var(--r)), 
  comma);
}

--r defines the radius so the size of each gradient will be equal to calc(2*var(--r)). Then we need to identify the position of each gradient (the x y).

Here as well, we need to consider some geometry formulas

x = calc(50% + 50%*cos(360deg*#{$i/$n})) 
y = calc(50% + 50%*sin(360deg*#{$i/$n}))

The final code will be:

$n: 6; /* the number of circles/petals */

.flower {
  --r: 30px; /* the radius of the small circles */
  width: calc(2*var(--r)*(1 + 1/sin(180deg/#{$n})));
  aspect-ratio: 1;
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#000 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: radial-gradient(#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0),{$m};
  background: /* you background coloration */
}

Note how the mask property takes the value generated using Sass in addition to the gradient that creates the big circle.

Our shape is done!

The size of the shape is controlled by the radius of the small circles but we can do the opposite which is probably more convenient since we generally want to control the width/height of our element.

.flower {
  --w: 200px; /* the size */
  --r: calc(var(--w)/(2*(1 + 1/sin(180deg/#{$n}))));
  width: var(--w);
  /* same as before */
}

We can even optimize the previous code a little and get rid the of --w variable. The latter is defining the width/height of the element and gradients can access such value using percentages we can write the code like below:

$n: 12; /* the number of circles/petals */

.flower {  
  width: 300px; /* the size */
  --r: calc(50%/(1 + 1/sin(180deg/#{$n}))); /* using percentage instead of --w */
  aspect-ratio: 1;
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#000 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0),#{$m};
}

Now by adjusting the width you control the size of the whole shape. Here is an interactive demo where you can resize the element and see how the shape adjusts automatically. Try it below with the resizer handle on the bottom right of the box:

Voilà! We did a nice flower shape without hack or complex code and you can easily control it by adjusting a few variables. You either use the above code or you consider my online generator to get the generated CSS without the variables and Sass.

Inverting the shape

Let’s try a different shape this time. It’s somehow the invert of the previous one where the circular part is going inside instead of outside. Well, a figure worth a thousand words.

spiky flower shape with purple to orange coloring

​​The code to get the above shape is the same as the previous one, but we are going to introduce mask-composite​. The idea is to cut the small circles from the bigger one which translates to a “subtract” composition.

Here is a figure to illustrate the process:

The code of mask will look like this:


mask: 
 radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0) subtract,
 #{$m};

And here is the one of the previous shape to compare both:


mask: 
 radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0),
 #{$m};

The only difference is the value of composition “subtract”. And since the remaining code is also the same, we can introduce a variable to control which one to use:


$n: 12; /* the number of circles/petals */

.flower {
  width: 300px;
  aspect-ratio: 1;
  --r: calc(50%/(1 + 1/sin(180deg/#{$n})));
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#000 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n})),#0000 0) var(--alt,),#{$m};
}
.alt {
  --alt: subtract;
}

The var(--alt,) will default to nothing when the variable is not specified and we get the code of the first shape. By adding the composition value, we get the second shape. Two different shapes with the same code.

Why not simply add mask-composite: subtract?

This won’t work because it will apply a “subtract” composition between all the gradient layers whereas we want the composition to happen only between the big circle and the small ones. If you want to use mask-composite it should have the following value:

mask-composite: subtract, add, add, ..., add;

We perform and “add” composition between the small circles (the default composition) then we “subtract” the result from the big circle. It’s clear that adding one value inside the shorthand version is easier.

The Border-Only Version

Now let’s tackle the border-only version of the previous shapes. We are also going to rely on mask-composite and the same code structure.

First of all, let’s introduce a new variable to control the border thickness and update the code that generates the small circles.

$n: 12; /* the number of circles/petals */

.flower {
  --b: 10px; /* the border thickness*/

  width: 300px;
  aspect-ratio: 1;
  --r: calc(50%/(1 + 1/sin(180deg/#{$n})));
  $m: (); /* empty variable */
  @for $i from 1 through ($n) { /* loop through the number of circles*/
    $m: append($m, 
      radial-gradient(50% 50%,#0000 calc(100% - var(--b)),#000 0 100%,#0000) no-repeat
      calc(50% + 50%*cos(360deg*#{$i/$n})) 
      calc(50% + 50%*sin(360deg*#{$i/$n}))
     / calc(2*var(--r)) calc(2*var(--r)), 
    comma);
  }
  mask: #{$m};
}

Nothing complex so far. Instead of full circles, we are getting a border-only version. This time we don’t want them to touch each other but rather overlap a bit to have a continuous shape.

We need to increase the radius a little from this

--r: calc(50%/(1 + 1/sin(180deg/#{$n})));

To this:

--r:calc((50% + var(--b)/(2*sin(180deg/#{$n})))/(1 + 1/sin(180deg/#{$n})));

Again some geometry stuff but you don’t really need to accurately understand all the formulas. I did the hard work to identify them and you only need to understand the main trick. In the end, all you have to do is update a few variables to control the shape or get the code from my generator.

The result so far:

Now, we use mask-composite with another gradient to hide some parts and get our final shapes. Here is a figure to illustrate the process for both shapes.

For the first shape:

And for the second one:

In both cases, I am introducing an extra gradient that will intersect with the small circles. The difference between each shape is the coloration of the extra gradient. In the first case, we have transparent inside and filling outside and for the second case, we have the opposite.

/* first shape */
mask:
 radial-gradient(100% 100%,#0000 calc(var(--r)/tan(180deg/#{$n}) - var(--b)/(2*tan(180deg/#{$n}))),#000 0) intersect, 
 #{$m};
/* second shape */
mask:
 radial-gradient(100% 100%,#000 calc(var(--r)/tan(180deg/#{$n}) - var(--b)/(2*tan(180deg/#{$n}))),#0000 0) intersect, 
 #{$m};

And here is the full code with both variations:

If you have some trouble visualizing how mask-composite works, I invite you to read the crash course written by Ana Tudor where you will find a more in-depth exploration.

One More Shape

Another flower? Let’s go!

blob like shape, sort of like a gear with smoothed over cogs.

This time, it’s your turn to figure out the code. Consider this as a small piece of homework to practice what we have learned together. As a hint, here is a figure that illustrates the mask-composite you need to perform, or maybe you will figure out another idea! If so don’t hesitate to share it in the comment section

Here is the code of my implementation but make a try before checking it so you can compare your method with mine. Take the time to study this last example because it will be the starting point of our second article.

Conclusion

I hope you enjoyed this little experimentation using CSS mask and trigonometric functions. You are probably not going to use such shapes in a real project but creating them is a good way to learn new features. If you have to remember one thing from this article it’s the use of mask-composite. It’s a powerful property that can help you create complex shapes.

It is worth noting that since we are using mask, we can easily apply our shape to image elements.

Don’t forget to bookmark my flower shapes generator so you can easily grab the code whenever you need it. I also have more CSS generators that invite you to check. Most of them rely on CSS mask as well and I have a detailed article linked to each one.

I will close this article with a few mesmerizing animations involving some flower shapes.

Article Series

]]>
https://frontendmasters.com/blog/creating-flower-shapes-using-css-mask-trigonometric-functions/feed/ 0 1021