A place to cache linked articles (think custom and personal wayback machine)
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.md 19KB

title: Quantity Queries for CSS url: http://alistapart.com/article/quantity-queries-for-css hash_url: 6ab93fddd6

Don’t you just hate documentaries that don’t deliver? They have enticing names like In Search of the Giant Squid, and tease you with shots of murky underwater shapes and excited scientists pointing far out to sea. You settle down to watch, eyes narrowed with suspicion, thinking, “I better see some squid or I’m writing an angry letter to the network.”

Sure enough, 90 minutes of interviews with bored-looking fishermen later, the presenter is forced to conclude, “No… no, we didn’t find any big squids. But maybe one day [majestic orchestral flourish].” Great. You wanted Finding Nemo and got Not Finding Nemo instead.

I wouldn’t do that to you, friends. This is your guide to creating style breakpoints for quantities of HTML elements, much as you already do with @media queries for viewport dimensions. I’m not pointing at some blurry specification in the distance or a twinkle in an implementer’s eye. We’re going to do this today, with CSS that’s already available.

Dynamic content

Responsive web design is primarily concerned with one variable: space. In testing responsive layouts, we take an amount of content and see which spaces it will successfully fit into. The content is deemed constant; the space, variable.

The @media query is the darling of responsive web design because it allows us to insert “breakpoints” wherever one layout strategy ceases to be viable and another should succeed it. However, it’s not just viewport dimensions, but the quantity of content that can put pressure on space.

Just as your end users are liable to operate devices with a multitude of different screen sizes, your content editors are liable to add and remove content. That’s what content management systems are for.  This makes Photoshop mockups of web pages doubly obsolete: they are snapshots of just one viewport, with content in just one state.

In this article, I will be outlining a technique to make CSS quantity-aware using specially formed selectors. I will be applying these selectors to one classic problem in particular: how to alter the display of items in a horizontal navigation menu when there are too many to suit the initial layout mode. That is, I will demonstrate how to switch from a display: table-cell to a display: inline-block layout when the number of items in the menu becomes “more than or equal to 6.”

I will not be relying on any JavaScript or template logic, and the menu’s list markup will remain devoid of class attribution. By using CSS only, the technique honors the separation of concerns principle, according to which content (HTML) and presentation (CSS) have clearly defined roles. Layout is CSS’s job and, where possible, CSS’s only.

Comparing the initial menu bar layout for fewer than six items with the layout for six or more items

The demonstration is available on CodePen and will be referred to throughout the article.

To help me illustrate this qualification of quantity, I’ll be employing diagrams of squids in the article to represent HTML elements. Green squids with ticks represent elements that match the CSS selector in question, red squids with crosses are unselected elements, and grayed-out squids denote elements that don’t exist.

A key for the three squid symbols to be used in following diagrams. A green squid (for selected elements), a red squid (for unselected elements) and a grey squid for elements that don't exist

Counting

The key to determining the quantity of elements in a given context is to count them. CSS doesn’t provide an explicit “counting API,” but we can solve the same problem with an inventive combination of selectors.

Counting to one

The :only-child selector provides a means to style elements if they appear in isolation. Essentially, it lets us “style all the child elements of a particular element, if counting those children returns 1 as the total.” Aside from its stablemate :only-of-type, it is the only simple selector that can be described as quantity-based.

In the following example, I use :only-of-type to add a special style to any buttons that are the only elements of their element type among sibling elements. I give these lone buttons an increased font-size because singularity suggests importance.


button { 
  font-size: 1.25em;
}

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

Here’s the crucial part. If I were to start out with one button, replete with a larger font size, and add buttons before or after it, each button would then adopt a smaller font size. The style of all the elements in the set is dependent on a quantity threshold of two: if there are “fewer than two” elements, the larger font size is honored. Take a look at that code again with the “fewer than two” notion in mind:


button {
  font-size: 1.25em;
}

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

The fewer than two logic means one selected element (green squid) becomes two unselected elements (red squids) when an element is added

If it feels more natural, you can turn the CSS logic on its head using negation and make the condition “more than one.”


/* "More than one" results in a smaller font size */
button {
  font-size: 2em;
}

button:not(:only-of-type) {
  font-size: 1.25em;
}

The more than one logic means one unselected element (red squid) becomes two selected elements (green squids) when an element is added

Quantity n

Styling elements based on the “more than one” and “fewer than two” thresholds is a neat trick, but a flexible “quantity query” interface would accept any quantity. That is, I should be able to style “more than or equal to n” for any value of n. Then I can style “more than or equal to 6” in our navigation menu.

With a view to achieving this final goal, what if I were able to style discrete quantities like “exactly 6 in total” or “exactly 745”? How would I go about that? I would need to use a selector that allowed me to traverse sets of elements of any quantity numerically.

Fortunately, the :nth-last-child(n) selector accepts the number “n”, enabling me to count sets of elements from the end of the set toward the beginning. For example, :nth-last-child(6) matches the element that is sixth from last among sibling elements.

Things get interesting when concatenating :nth-last-child(6) with :first-child, introducing a second condition. In this case, I am looking for any element that is both the sixth element from the end and the first element.


li:nth-last-child(6):first-child {
  /* green squid styling */
}

If this element exists, the set of elements must be exactly six in quantity. Somewhat radically, I have written CSS that tells me how many elements I am looking at.

Of six squids, the first is green and the rest red. The first is subject to the nth-last-child(6) selector as well as the first-child selector

All that remains is to leverage this key element to style the remaining elements in the set. For this, I employ the general sibling combinator.

Six green squids because the first green squid is combined with the general sibling combinator to make all the red squids that follow green

If you’re not familiar with the general sibling combinator, the ~ li in li:nth-last-child(6):first-child ~ li means “any li elements that occur after li:nth-last-child(6):first-child.” In the following example, the elements each adopt a green font color if there are precisely six of them in total.


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

More than or equal to 6

Targeting a discrete quantity—whether it’s 6, 19, or 653—is not especially useful because it pertains to such a specific situation. Using discrete widths rather than min-width or max-width in our @media queries would be similarly unhelpful:


@media screen and (width: 500px) {
  /* styles for exactly 500px wide viewports */
}

In the navigation menu, I really want to switch layouts at a threshold: a quantity watershed. I want to switch at six or more items—not exactly six items. When I reach that threshold, I would like to change from a distributed table layout to a simpler, wrappable inline-block configuration. Importantly, I would like to retain that switched configuration as the number of items further increases.

The question is, how does one begin to construct such a selector? It’s a question of offsets.

The n+6 argument

Another arithmetical argument adoptable by the :nth-child() selector takes the form “n + [integer]”. For example, :nth-child(n+6) styles all the elements in a set starting from the sixth.

A set of red squids that become green at the sixth squid for the remainder of the set (which can be of any size), counting upwards.

Though this has conceivable applications all its own, it’s not a “quantity-aware” selection method as such: we’re not styling anything because there are six elements or more in total; we’re just styling the ones that happen to enumerate higher than five.

To begin solving the problem properly, what we really need is to create a set of elements that excludes the last five items. Using the opposite of :nth-child(n+6):nth-last-child(n+6)—I can apply the switched layout properties to all “last elements” starting from the sixth, counting back toward the beginning of the set.


li:nth-last-child(n+6) {
  /* properties here */
}

This omits the last five items from a set of any length, meaning that when you reduce the length of the set below six, you cease to see any selected items. It’s a sort of “sliding doors” effect.

A set of green squids (to the left) and red squids (to the right) become a set of just red squids when the set becomes fewer than six in number

If, indeed, the set is greater than or equal to six in total, then all that remains is to style those last five items as well. This is easy: where there are more than six items, one or more items that “return true” (in JavaScript-speak) for the nth-last-child(n+6) condition must exist. Any and all of these extant elements can be combined with “~” to affect all items (including the last five) that follow it.

When a set of red squids has squids added to it, the squids to the right of the set become green and can be used to make the rest of the red squids green too (with the general sibling combinator)

The surprisingly terse solution to our problem is this:


li:nth-last-child(n+6),
li:nth-last-child(n+6) ~ li {
  /* properties here */
}

Naturally, 6 can be replaced with any positive integer, even 653,279.

Fewer than or equal to n

As in the earlier :only-of-type example, you can turn the logic on its head, switching from “more than or equal to n” to “fewer than or equal to n.” Which brand of logic you use depends on which state you consider the more natural default state. “Fewer than or equal to n” is possible by negating n and reinstating the :first-child condition.


li:nth-last-child(-n+6):first-child,
li:nth-last-child(-n+6):first-child ~ li {
  /* properties here */
}

In effect, the use of “-” switches the direction of the selection: instead of pointing toward the start from the sixth, it points toward the end from the sixth. In each case, the selector is inclusive of the sixth item.

nth-child versus nth-of-type

Note that I am using :nth-child() and :nth-last-child() in the preceding examples, not :nth-of-type() and :nth-last-of-type(). Because I am dealing in <li> elements and <li>s are the only legitimate children of <ul>s, :last-child() and :last-of-type() would both work here.

The :nth-child() and :nth-of-type() families of selectors have different advantages depending on what you are trying to achieve. Because :nth-child() is element agnostic, you could apply the described technique across different element type siblings:


<div class="container">

  <p>...</p>

  <p>...</p>

  <blockquote>...</blockquote>

  <figure>...</figure>

  <p>...</p>

  <p>...</p>

</div>


.container > :nth-last-child(n+3),
.container > :nth-last-child(n+3) ~ * {
  /* properties here */
}

(Note how I am using the universal selector to maintain element agnosticism here. :last-child(n+3) ~ * means “any element of any type following :last-child(n+3).”)

The advantage of :nth-last-of-type(), on the other hand, is that you are able to target groups of like elements where other siblings of different types are present. For example, you could target the quantity of paragraphs in the following snippet, despite them being bookended by a <div> and a <blockquote>.


<div class="container">

  <div>...</div>

  <p>...</p>

  <p>...</p>

  <p>...</p>

  <p>...</p>

  <p>...</p>

  <p>...</p>

  <p>...</p>

  <blockquote>...</blockquote>

</div>


p:nth-last-of-type(n+6),
p:nth-last-of-type(n+6) ~ p {
  /* properties here */
}

Selector support

All of the CSS2.1 and CSS3 selectors used in this article are supported in Internet Explorer 9 and above, including all reasonably recent mobile/handheld stock browsers.

Internet Explorer 8 support is good for most selector types, but technically partial, so you might want to consider a JavaScript polyfill. Alternately, you could pair the selectors for the “safer” of the layout strategies with IE9-specific classes. In the case of the navigation menu, the safer option is the one catering to more items, using inline-block. The declaration block would 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;
  /* etc */
}

In the real world

Suppose our navigation menu belongs to a content-managed site. Depending on who is administering the theme, it will be populated with a greater or fewer number of options. Some authors will keep things simple with just “Home” and “About” links provided, while others will cram their menu full of custom page and category options.

By providing alternative layouts depending on the number of menu items present, we increase the elegance with which we tolerate different implementations of the theme: we address variable content as we might variable screen dimensions.

Comparing the initial menu bar layout for fewer than six items with the layout for six or more items

So, there you have it: squid ahoy! You can now add quantity as a styling condition to your repertoire.

Content-independent design

Responsive web design solves an important problem: it makes the same content comfortably digestible between different devices. For folks to receive different content just because they have different devices would be unacceptable. Similarly, it’s unacceptable for a design to dictate the nature of the content. We wouldn’t tell an editor, “Lose that, will you? It makes the design look wrong.”

But what form the content takes, and how much of it there is at any one time, is frequently indeterminate—another unknown. And we can’t always rely on text wrapping and truncation scripts. To get a real handle on content independence, we need to develop new tools and techniques. Quantity queries are just one idea.

Web design is about mutability, difference, uncertainty. It’s about not knowing. Uniquely, it is a mode of visual design not about manifesting a form, but about anticipating the different forms something might take. To some it is unbearably perplexing, but to you and me it is a challenge to relish. Like the elusive giant squid, it is a seriously slippery customer.