Quantitative CSS selectors

Original author: Heydon Pickering
  • Transfer
Have you ever wanted to calculate directly in CSS code, in a menu, 4 elements or 10? For four, ask them a width of 25%, and if you have ten, press them together and reduce the indentation?
As it turned out, CSS can work with a different number of elements, allowing you to get rid of headaches and unnecessary js code.




Dynamic content


Responsive design is usually associated with one variable - space. When we test by responding to the grid, we take a certain amount of content and see how much space it takes, how it looks and where it fits, and where not. That is, content is considered a constant, and space is considered a variable.

Media query are the basis of the responsive design, as they allow you to draw borders, passing which one grid changes to another. However, the arrangement of elements and the space around them can be affected not only by the screen size, but also by the content itself.

Just as site visitors can use many devices with different screen sizes, your content managers and editors can add or remove content. They even have whole CMS for this. That is why page designs in Photoshop have lost their relevance - they always have a fixed screen width and always fixed content.

In this article, I will describe a technique for creating CSS without problems with the number (counting) of elements. This is achieved through specially designed selectors. I will apply them in the contest of solving the classical problem: how to divide the layout of elements in the horizontal menu of the site when there are few and many elements in it. For example, how to achieve that with 6 or more menu items the elements had the style display: inline-block, and with a smaller number - display: table-cell.

I will not use templating or js, I don’t even need to register classes on menu items. The technique of using only CSS selectors follows the principle of separation of interests , according to which content (HTML) and display (CSS) have clearly defined roles. Markup is the work of CSS and, if possible, CSS alone.



The demo is available on CodePen and will be mentioned throughout the article.

To simplify understanding, I will use squid pictures instead of HTML tags. Green squids will be for elements that fall under a particular selector, red squids will be for those that do not fall, and gray will mean that the element does not exist.



Score


To find out the number of elements, they need to be counted.
Note per
Captain
CSS does not provide an explicit “counting API”, but we can solve this problem bypassing by combining selectors as needed.

Count to one


The: only-child selector is triggered on elements, which are always one piece. In essence, this allows us to “apply styles to all children of a particular element, if there are exactly 1 in total.” This is the only simple selector that can be described as “counting” (except, of course, similar to it: only-of-type).

In the example described below, I use: only-of-type to apply styles to all buttons, which are the only child buttons among their neighbors.

button { 
  font-size: 1.25em;
}
button:only-of-type {
  font-size: 2em;
}

It is important to understand that line ordering is key here. In addition, it is useful to see this code in terms of a less-than-two



select : Similarly, we can now make a more-than-one select using negation .

/* "больше-чем-один" будут иметь уменьшенный размер шрифта */
button {
  font-size: 2em;
}
button:not(:only-of-type) {
  font-size: 1.25em;
}



N items


Applying styles based on more-than-one and less-than-two is a clever trick, but we need a more flexible tool that allows us to operate with any number. We want to use selects “greater than or equal to N” for any N. In addition, we want to have the select “exactly 745” or “total exactly 6”.

To do this, we have a selector : nth-last-child (n) , into which we pass any number as a parameter. It allows you to count so many elements back from the end of the list. For example: nth-last-child (6) will select the element that is the sixth from the end among its neighboring elements.

Everything becomes more interesting when we combine: nth-last-child (6) and: first-child, resulting in all the elements that are sixth from the end and first from the beginning.

li:nth-last-child(6):first-child {
  /* зеленый кальмар */
}

If such an element exists, it will mean that we have exactly 6 elements. So I wrote CSS code that can tell how many elements I see in front of me.



It remains now to use this “sixth from the end and first from the beginning” element at the same time to also select all the other 5 elements. To do this, I will use the common neighboring combinator .



If you are not familiar with this combinator, then I explain ~ li in the li: nth-last-child (6) select: first-child ~ li means "any li that comes after li: nth-last-child (6): first-child. " In our case, the elements will have a green font color if there are exactly 6 of them.

li:nth-last-child(6):first-child, 
li:nth-last-child(6):first-child ~ li {
  color: green;
}

Greater than or equal to 6


Selecting a fixed amount, be it 6, 19 or 653, is not very useful, since such a need is very rare. It’s like in media queries - it’s not very convenient to use fixed width instead of min-width or max-width:

@media screen and (width: 500px) {
  /* стили для ширины вьюпорта ровно в 500px */
}

In the navigation menu, I want to switch styles with a border based on the number of elements, for example, change the desired styles if I have 6 or more elements (and not exactly six).

The question is how to make such a selector? and this is a matter of bias.

Parameter n + 6


The selector: nth-child () can take as a parameter not only a number, but also the formula “n + [number]”. For example: nth-child (n + 6) will be applied to all elements starting from the sixth.


Although this selector is a powerful tool in itself, it does not allow you to operate with quantity. It will not apply when we have more than six elements, but simply to elements whose number is more than five.

To get around this problem, we need to create a select that selects all the elements except the last five. Using the reverse to: nth-child (n + 6) select: nth-last-child (n + 6) we can select all the elements “from the sixth from the end to the very first from the beginning”.

li:nth-last-child(n+6) {
  /* здесь стили */
}

Such a selection cuts off the last five elements from a set of any length, which means that if you have less than six elements, then nothing will get into the select.



If there are six or more elements in the list, it remains for us to add the remaining five elements to the selection. This is easy - if there are more than six elements, then the nth-last-child (n + 6) condition is triggered, and by combining it with "~" we can select all the elements we need.



Such a short record is the solution to our problem:

li:nth-last-child(n+6),
li:nth-last-child(n+6) ~ li {
  /* здесь стили */
}

Of course, there can be any positive integer, even 653.279.

Less or n


As in the previous example with: only-of-type, the selector can be used both ways, both as "greater than or equal to N" and as "less than or equal to N". Which option will you use? Depends on which grid you will consider the main.
In the case of "Less or N", we take n with a minus and add the condition: first-child.

li:nth-last-child(-n+6):first-child,
li:nth-last-child(-n+6):first-child ~ li {
  /* здесь стили */
}

As a result, the use of “-” changes the direction of the select: instead of counting from the beginning to six, the countdown will go from the end to six. In both cases, the sixth element will be included in the selection.

nth-child vs nth-of-type


Notice that in the previous examples I used: nth-child () and: nth-last-child (), not: nth-of-type () with: nth-last-of-type (). Since I have selected tags
  • and right descendants
      only they can be: last-child () and: last-of-type () are equally effective.

      Both groups of selects: nth-child () and: nth-of-type () have their advantages, based on what you want to achieve. Since: nth-child () is not tied to a specific tag, the technique described above can be applied to mixed child elements:

      ...

      ...

      ...
      ...

      ...

      ...



      .container > :nth-last-child(n+3),
      .container > :nth-last-child(n+3) ~ * {
        /* здесь стили */
      }
      

      (Note that in order not to be attached to tags, I use a universal selector .: Last-child (n + 3) ~ *. In this case, it means “any element of any tag following: last-child (n + 3) . ”

      On the other hand, the advantage: nth-last-of-type () is that you can select elements with one tag among many other neighboring ones. For example, you can count the number of tags

      despite the fact that they are on the same heap

      and
      .

      ...

      ...

      ...

      ...

      ...

      ...

      ...

      ...

      ...


      p:nth-last-of-type(n+6),
      p:nth-last-of-type(n+6) ~ p {
        /* здесь стили */
      }
      

      Browser support



      All CSS2.1 and CSS3 selectors used in the article are supported in Internet Explorer 9 and higher , as well as all modern mobile and desktop browsers.

      Internet Explorer 8 is good at supporting most selects, but it won't hurt to think about polyfill on js. Alternatively, you can set up special classes for older versions of IE and assign them to your selectors. In the case of the navigation menu, the code will look something like this:

      nav li:nth-last-child(n+6),
      nav li:nth-last-child(n+6) ~ li, 
      .lt-ie9 nav li {
        display: inline-block;
        /* и т.д. */
      }
      

      In the real world



      Suppose our navigation menu belongs to a site with a CMS. Depending on who fills it and uses it, the menu will have a different number of elements. Someone is trying not to complicate it, and “Home” and “About” are enough for him, while someone wants to cram everything he has on the site into the menu.

      By creating styles that take into account different grids for a different number of elements in the menu, we make layout more coherent and suitable for a wide variety of formats - whatever the content, we have everything taken into account.



      Congratulations, now your CSS arsenal has the ability to handle the number of elements.

      Content Independent Design


      Responsive design solves one important problem: it allows you to comfortably place the same content on many different devices. It would be unacceptable to see different content just because you have the wrong screen size. It is also unacceptable to dictate to the designer how many menu items a site should be sharpened. “Don't draw like that, we’ll have the whole grid going if there are so many elements.”

      What form the content will take and how much it will be at a particular moment in time - no one knows. And we cannot always rely on scripts or text trimming, this is wrong. It’s right to be independent of the amount of content and create the necessary mechanisms and tools for this. Quantitative selectors are just one of the ideas for this.

      Web design is always variability, flexibility and uncertainty. In it, you rather do not know than you know. It is unique in that it is a kind of visual design that does not define forms, but anticipates what forms something can take. For some, this is unacceptable and incomprehensible, but for you and me it is a challenge and a pleasure.

  • Also popular now: