Scope syntax is now available for container style queries and if() | CSS tricks

Scope syntax is now available for container style queries and if() | CSS tricks

Scope syntax is nothing new. We can already use it with media queries to query the dimensions and resolutions of the viewport, as well as the container mate queries to request container dimensions. Being able to use it with a container style queries (which we can do from Chrome 142) means we can compare literal numeric values ​​with numeric values ​​tokenized by custom properties or the attr() function.

Additionally, this feature is coming to the if() function too.

Here’s a quick demo showing how to use the range syntax in both contexts to compare a custom property (--lightness) to a literal value (50%):

#container {
  /* Choose any value 0-100% */
  --lightness: 10%;

  /* Applies it to the background */
  background: hsl(270 100% var(--lightness));

  color: if(
    /* If --lightness is less than 50%, white text */
    style(--lightness < 50%): white;
    /* If --lightness is more than or equal to 50%, black text */
    style(--lightness >= 50%): black
  );

  /* Selects the children */
  * {
    /* Specifically queries parents */
    @container style(--lightness < 50%) {
      color: white;
    }

    @container style(--lightness >= 50%) {
      color: black;
    }
  }
}

Again, you want Chrome 142 or later to see this work:

Both methods do the same thing, but in slightly different ways.

Let’s take a closer look.

Scope syntax with custom properties

In the next demo coming up, I have the if() things, leaving only the container style queries. What happens here is that we have created a custom property called --lightness on the #container. It is not possible to query the value of a normal property. Therefore, we store this (or part of it) as a custom property and then use it to form the HSL-formatted value of the property. background.

#container {
  /* Choose any value 0-100% */
  --lightness: 10%;

  /* Applies it to the background */
  background: hsl(270 100% var(--lightness));
}

Then we select the children of the container and declare them conditionally color using container-style queries. Specifically, if the --lightness owned by #container (and by extension the background) is less than 50%we set the color Unpleasant white. Or, if it is more than or equal to 50%we set the color Unpleasant black.

#container {
  /* etc. */

  /* Selects the children */
  * {
    /* Specifically queries parents */
    @container style(--lightness < 50%) {
      color: white;
    }

    @container style(--lightness >= 50%) {
      color: black;
    }
  }
}

/explanation Note that we have the @container at rules for the #container block it, because then we would ask questions --lightness on the container of #container (where it doesn’t exist) and then further (where it also doesn’t exist).

Before scope syntax came to container-style queries, we could only query specific values, so scope syntax makes container-style queries much more useful.

In contrast, the if()-based declaration would work in both blocks:

#container {
  --lightness: 10%;
  background: hsl(270 100% var(--lightness));

  /* --lightness works here */
  color: if(
    style(--lightness < 50%): white;
    style(--lightness >= 50%): black
  );

  * {
    /* And here! */
    color: if(
      style(--lightness < 50%): white;
      style(--lightness >= 50%): black
    );
  }
}

So given that container style queries only look upwards the cascade (while if() also looks for custom properties declared within the same CSS rule), why should we use container style queries at all? Aside from personal preference, container queries allow us to define a specific containment context using the container-name CSS property:

#container {
  --lightness: 10%;
  background: hsl(270 100% var(--lightness));

  /* Define a named containment context */
  container-name: myContainer;

  * {
    /* Specify the name here */
    @container myContainer style(--lightness < 50%) {
      color: white;
    }

    @container myContainer style(--lightness >= 50%) {
      color: black;
    }
  }
}

With this version, if the @container at-rule can’t find it --lightness on myContainerthe block does not run. If we wanted to @container to look further into the cascade, we just need to indicate container-name: myContainer further down the cascade. The if() function does not allow this, but container queries allow us to control the scope.

Scope syntax with the attr() CSS function

We can also extract values ​​from HTML attributes using the attr() CSS function.

In the HTML below I created an element with a data attribute called data-notifs whose value represents the number of unread notifications a user has:

We want to select [data-notifs]::after so we can put the number in there [data-notifs] using the content CSS property. This in turn is where we get the @container at rules, with [data-notifs] serve as a container. I have one too height and matching border-radius for styling:

[data-notifs]::after {
  height: 1.25rem;
  border-radius: 1.25rem;

  /* Container style queries here */
}

Now the container style query logic. In the first case, it is quite clear that if the number of notifications is 1-2 digits (or, as it is expressed in the query, is less than or equal to 99), content: attr(data-notifs) inserts the number of the data-notifs attribute while aspect-ratio: 1 / 1 ensures that the width matches the height and forms a circular notification badge.

In the second query, which matches if the number is greater than 99we switch to content: "99+" because I don't think a notification badge can handle four digits. We'll also add some inline padding instead of one widthsince the circle can't even fit three characters.

To summarize, we're basically using this container-style query logic to determine both content and style, which is really cool:

[data-notifs]::after {
  height: 1.25rem;
  border-radius: 1.25rem;

  /* If notification count is 1-2 digits */
  @container style(attr(data-notifs type()) <= 99) {

    /* Display count */
    content: attr(data-notifs);

    /* Make width equal the height */
    aspect-ratio: 1 / 1;
  }

  /* If notification count is 3 or more digits */
  @container style(attr(data-notifs type()) > 99) {
    /* After 99, simply say "99+" */
    content: "99+";

    /* Instead of width, a little padding */
    padding-inline: 0.1875rem;
  }
}

But you are probably wondering why, when we read the value in the container style queries, it is written as attr(data-notifs type() instead of attr(data-notifs). The reason is that when we don't specify a data type (or unit), you can read all about the recent changes in attr() here), the value is parsed as a string. This is fine if we output the value with content: attr(data-notifs)but if we are to compare it to 99we need to parse it as a number (although type() would also work).

In fact, all comparative syntax data must be of the same data type (although they do not have to use the same units). Supported data types include , , , , , And . In the earlier example, since modern times we have been able to express lightness without units hsl() The syntax supports that, but we need to be consistent with it and ensure that all comparatives are also unitless:

#container {
  /* 10, not 10% */
  --lightness: 10;

  background: hsl(270 100 var(--lightness));

  color: if(
    /* 50, not 50% */
    style(--lightness < 50): white;
    style(--lightness >= 50): black
  );

  * {
    /* 50, not 50% */
    @container style(--lightness < 50) {
      color: white;
    }

    @container style(--lightness >= 50) {
      color: black;
    }
  }
}

Remark: This example of counting notifications is not suitable for that if()because you would have to include the logic for every relevant CSS property, but it is possible and would use the same logic.

Scope syntax with literals

We can also compare literal values, for example 1em Unpleasant 32px. Yes, they are different entities, but remember that they only need to be the same data type and are both valid CSS S.

In the following example we set the font-size of the element to 31px. The inherits this font-sizeand since then 1em is equal to the font-size from the parent, 1em in the context of is too 31px. With me so far?

According to the if() logic, if 1em equals less than 32pxthe font-weight is smaller (to be an exaggeration, let's say 100), while if 1em is equal to or greater than 32pxwe set the font-weight to a thick one 900. If we use the font-size report then 1em calculates the default of the user agent 32pxAnd neither condition matches, so the font-weight to also calculate to the default value of the user agent, which applies to all heads 700.

Basically, the idea is that if we mess with the standard font-size of the , then we declare an optimized font-weight to maintain readability and prevent small bold and large thin text.

h1 {
  /*
    The default value is 32px,
    but we overwrite it to 31px,
    causing the first if() condition to match
  */
  font-size: 31px;

  span {
    /* Here, 1em is equal to 31px */

    font-weight: if(
      style(1em < 32px): 100;
      style(1em > 32px): 900
    );
  }
}

CSS queries have come a long way, haven't they?

In my opinion, the scope syntax comes to container style queries and the if() function represents CSS's biggest leap in terms of conditional logic, especially considering that it can be combined with media queries, function queries, and other types of container queries (don't forget to container-type when combined with container size queries). In fact it would now be a Great time to refresh your questions, so as a little parting gift here are some links for more information:

#Scope #syntax #container #style #queries #CSS #tricks

Similar Posts

Leave a Reply

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