Source originale du contenu
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.
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.
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;
}
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;
}
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.
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.
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.
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.
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.
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.
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.