A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.md 19KB

4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. title: Quantity Queries for CSS
  2. url: http://alistapart.com/article/quantity-queries-for-css
  3. hash_url: 6ab93fddd6436fc39962c832c70c7f07
  4. <p>Don’t you just hate documentaries that don’t deliver? They have enticing names like <cite>In Search of the Giant Squid</cite>, 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.”</p>
  5. <p>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 <cite>Finding Nemo</cite> and got <cite>Not Finding Nemo</cite> instead.</p>
  6. <p>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 <code>@media</code> 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 <em>today</em>, with CSS that’s already available.</p>
  7. <h2>Dynamic content</h2>
  8. <p><a href="http://www.abookapart.com/products/responsive-web-design">Responsive web design</a> 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.</p>
  9. <p>The <code>@media</code> 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 <em>quantity</em> of content that can put pressure on space. </p>
  10. <p>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.</p>
  11. <p>In this article, I will be outlining a technique to make CSS <em>quantity-aware</em> 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 <code>display: table-cell</code> to a <code>display: inline-block</code> layout when the number of items in the menu becomes “more than or equal to 6.”</p>
  12. <p>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 <a href="http://www.w3.org/TR/html-design-principles/#separation-of-concerns">separation of concerns</a> principle, according to which content (HTML) and presentation (CSS) have clearly defined roles. Layout is CSS’s job and, where possible, CSS’s only.</p>
  13. <figure>
  14. <img src="http://alistapart.com/d/415/fig1_menu.png" alt="Comparing the initial menu bar layout for fewer than six items with the layout for six or more items"/>
  15. </figure>
  16. <p>The demonstration is <a href="http://codepen.io/heydon/pen/QwjGZp">available on CodePen</a> and will be referred to throughout the article.</p>
  17. <p>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.</p>
  18. <figure>
  19. <img src="http://alistapart.com/d/415/fig2_squid.png" alt="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"/>
  20. </figure>
  21. <h2>Counting</h2>
  22. <p>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.</p>
  23. <h3>Counting to one</h3>
  24. <p>The <code>:only-child</code> 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 <code>:only-of-type</code>, it is the only simple selector that can be described as quantity-based.</p>
  25. <p>In the following example, I use <code>:only-of-type</code> to add a special style to any buttons that are the <em>only</em> elements of their element type among sibling elements. I give these lone buttons an increased <code>font-size</code> because singularity suggests importance.</p>
  26. <pre><code class="language-css">
  27. button {
  28. font-size: 1.25em;
  29. }
  30. button:only-of-type {
  31. font-size: 2em;
  32. }
  33. </code></pre>
  34. <p>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, <em>each</em> 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:</p>
  35. <pre><code class="language-css">
  36. button {
  37. font-size: 1.25em;
  38. }
  39. button:only-of-type {
  40. font-size: 2em;
  41. }
  42. </code></pre>
  43. <figure>
  44. <img src="http://alistapart.com/d/415/fig3_fewerthan.png" alt="The fewer than two logic means one selected element (green squid) becomes two unselected elements (red squids) when an element is added"/>
  45. </figure>
  46. <p>If it feels more natural, you can turn the CSS logic on its head using <a href="http://css-tricks.com/almanac/selectors/n/not/">negation</a> and make the condition “more than one.”</p>
  47. <pre><code class="language-css">
  48. /* "More than one" results in a smaller font size */
  49. button {
  50. font-size: 2em;
  51. }
  52. button:not(:only-of-type) {
  53. font-size: 1.25em;
  54. }
  55. </code></pre>
  56. <figure>
  57. <img src="http://alistapart.com/d/415/fig4_morethan.png" alt="The more than one logic means one unselected element (red squid) becomes two selected elements (green squids) when an element is added"/>
  58. </figure>
  59. <h3>Quantity <i>n</i></h3>
  60. <p>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 <i>n</i>” for any value of <i>n</i>. Then I can style “more than or equal to 6” in our navigation menu.</p>
  61. <p>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.</p>
  62. <p>Fortunately, the <a href="http://css-tricks.com/almanac/selectors/n/nth-last-child/"><code>:nth-last-child(n)</code> selector</a> accepts the number “<i>n</i>”, enabling me to count sets of elements from the <em>end</em> of the set toward the beginning. For example, <code>:nth-last-child(6)</code> matches the element that is sixth from last among sibling elements.</p>
  63. <p>Things get interesting when concatenating <code>:nth-last-child(6)</code> with <code>:first-child</code>, introducing a second condition. In this case, I am looking for any element that is <em>both</em> the sixth element from the end <em>and</em> the first element.</p>
  64. <pre><code class="language-css">
  65. li:nth-last-child(6):first-child {
  66. /* green squid styling */
  67. }
  68. </code></pre>
  69. <p>If this element exists, the set of elements <em>must be exactly six</em> in quantity. Somewhat radically, I have written CSS that tells me how many elements I am looking at.</p>
  70. <figure>
  71. <img src="http://alistapart.com/d/415/fig5_firstchild.png" alt="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"/>
  72. </figure>
  73. <p>All that remains is to leverage this key element to style the remaining elements in the set. For this, I employ the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_selectors">general sibling combinator</a>.</p>
  74. <figure>
  75. <img src="http://alistapart.com/d/415/fig6_firstchild_li.png" alt="Six green squids because the first green squid is combined with the general sibling combinator to make all the red squids that follow green"/>
  76. </figure>
  77. <p>If you’re not familiar with the general sibling combinator, the <code>~ li</code> in <code>li:nth-last-child(6):first-child ~ li</code> means “any <code>li</code> elements that occur after <code>li:nth-last-child(6):first-child</code>.” In the following example, the elements each adopt a green font color if there are <em>precisely</em> six of them in total.</p>
  78. <pre><code class="language-css">
  79. li:nth-last-child(6):first-child,
  80. li:nth-last-child(6):first-child ~ li {
  81. color: green;
  82. }
  83. </code></pre>
  84. <h2>More than or equal to 6</h2>
  85. <p>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 <code>min-width</code> or <code>max-width</code> in our <code>@media</code> queries would be similarly unhelpful:</p>
  86. <pre><code class="language-css">
  87. @media screen and (width: 500px) {
  88. /* styles for exactly 500px wide viewports */
  89. }
  90. </code></pre>
  91. <p>In <a href="http://alistapart.com/d/415/content-aware-navigation/">the navigation menu</a>, I really want to switch layouts at a <em>threshold</em>: a quantity watershed. I want to switch at six <em>or more</em> items—not <em>exactly</em> six items. When I reach that threshold, I would like to change from a distributed table layout to a simpler, wrappable <code>inline-block</code> configuration. Importantly, I would like to <em>retain</em> that switched configuration as the number of items further increases. </p>
  92. <p>The question is, how does one begin to construct such a selector? It’s a question of <em>offsets</em>.</p>
  93. <h3>The <i>n</i>+6 argument</h3>
  94. <p>Another arithmetical argument adoptable by the <code>:nth-child()</code> selector takes the form “<i>n</i> + [integer]”. For example, <code>:nth-child(n+6)</code> styles all the elements in a set starting from the sixth.</p>
  95. <figure>
  96. <img src="http://alistapart.com/d/415/fig7_adinfinitum.png" alt="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."/>
  97. </figure>
  98. <p>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.</p>
  99. <p>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 <code>:nth-child(n+6)</code>—<code>:nth-last-child(n+6)</code>—I can apply the switched layout properties to all “last elements” <em>starting from the sixth</em>, counting back toward the beginning of the set.</p>
  100. <pre><code class="language-css">
  101. li:nth-last-child(n+6) {
  102. /* properties here */
  103. }
  104. </code></pre>
  105. <p>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.</p>
  106. <figure>
  107. <img src="http://alistapart.com/d/415/fig8_slidingdoors.png" alt="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"/>
  108. </figure>
  109. <p>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 <code>nth-last-child(n+6)</code> condition must exist. Any and all of these extant elements can be combined with “<code>~</code>” to affect all items (including the last five) that follow it.</p>
  110. <figure>
  111. <img src="http://alistapart.com/d/415/fig9_breakpoint.png" alt="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)"/>
  112. </figure>
  113. <p>The surprisingly terse solution to our problem is this:</p>
  114. <pre><code class="language-css">
  115. li:nth-last-child(n+6),
  116. li:nth-last-child(n+6) ~ li {
  117. /* properties here */
  118. }
  119. </code></pre>
  120. <p>Naturally, 6 can be replaced with any positive integer, even 653,279.</p>
  121. <h2>Fewer than or equal to <i>n</i></h2>
  122. <p>As in the earlier <code>:only-of-type</code> example, you can turn the logic on its head, switching from “more than or equal to <i>n</i>” to “fewer than or equal to <i>n</i>.” Which brand of logic you use depends on which state you consider the more natural default state. “Fewer than or equal to <i>n</i>” is possible by negating <i>n</i> and reinstating the <code>:first-child</code> condition.</p>
  123. <pre><code class="language-css">
  124. li:nth-last-child(-n+6):first-child,
  125. li:nth-last-child(-n+6):first-child ~ li {
  126. /* properties here */
  127. }
  128. </code></pre>
  129. <p>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.</p>
  130. <h3><code>nth-child</code> versus <code>nth-of-type</code></h3>
  131. <p>Note that I am using <code>:nth-child()</code> and <code>:nth-last-child()</code> in the preceding examples, not <code>:nth-of-type()</code> and <code>:nth-last-of-type()</code>. Because I am dealing in <code>&lt;li&gt;</code> elements and <code>&lt;li&gt;</code>s are the only legitimate children of <code>&lt;ul&gt;</code>s, <code>:last-child()</code> and <code>:last-of-type()</code> would both work here.</p>
  132. <p>The <code>:nth-child()</code> and <code>:nth-of-type()</code> families of selectors have different advantages depending on what you are trying to achieve. Because <code>:nth-child()</code> is element agnostic, you could apply the described technique across different element type siblings:</p>
  133. <pre><code class="language-markup">
  134. &lt;div class="container"&gt;
  135. &lt;p&gt;...&lt;/p&gt;
  136. &lt;p&gt;...&lt;/p&gt;
  137. &lt;blockquote&gt;...&lt;/blockquote&gt;
  138. &lt;figure&gt;...&lt;/figure&gt;
  139. &lt;p&gt;...&lt;/p&gt;
  140. &lt;p&gt;...&lt;/p&gt;
  141. &lt;/div&gt;
  142. </code></pre>
  143. <pre><code class="language-css">
  144. .container &gt; :nth-last-child(n+3),
  145. .container &gt; :nth-last-child(n+3) ~ * {
  146. /* properties here */
  147. }
  148. </code></pre>
  149. <p>(Note how I am using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors">the universal selector</a> to maintain element agnosticism here. <code>:last-child(n+3) ~ *</code> means “any element of any type following <code>:last-child(n+3)</code>.”)</p>
  150. <p>The advantage of <code>:nth-last-of-type()</code>, 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 <code>&lt;div&gt;</code> and a <code>&lt;blockquote&gt;</code>.</p>
  151. <pre><code class="language-markup">
  152. &lt;div class="container"&gt;
  153. &lt;div&gt;...&lt;/div&gt;
  154. &lt;p&gt;...&lt;/p&gt;
  155. &lt;p&gt;...&lt;/p&gt;
  156. &lt;p&gt;...&lt;/p&gt;
  157. &lt;p&gt;...&lt;/p&gt;
  158. &lt;p&gt;...&lt;/p&gt;
  159. &lt;p&gt;...&lt;/p&gt;
  160. &lt;p&gt;...&lt;/p&gt;
  161. &lt;blockquote&gt;...&lt;/blockquote&gt;
  162. &lt;/div&gt;
  163. </code></pre>
  164. <pre><code class="language-css">
  165. p:nth-last-of-type(n+6),
  166. p:nth-last-of-type(n+6) ~ p {
  167. /* properties here */
  168. }
  169. </code></pre>
  170. <h2>Selector support</h2>
  171. <p>All of the CSS2.1 and CSS3 selectors used in this article are supported in <a href="http://caniuse.com/#feat=css-sel3">Internet Explorer 9 and above</a>, including all reasonably recent mobile/handheld stock browsers. </p>
  172. <p>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 <a href="http://nicolasgallagher.com/better-conditional-classnames-for-hack-free-css/">IE9-specific classes</a>. In the case of the navigation menu, the safer option is the one catering to more items, using <code>inline-block</code>. The declaration block would look something like this:</p>
  173. <pre><code class="language-css">
  174. nav li:nth-last-child(n+6),
  175. nav li:nth-last-child(n+6) ~ li,
  176. .lt-ie9 nav li {
  177. display: inline-block;
  178. /* etc */
  179. }
  180. </code></pre>
  181. <h2>In the real world</h2>
  182. <p>Suppose our <a href="http://alistapart.com/d/415/content-aware-navigation/">navigation menu</a> 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. </p>
  183. <p>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.</p>
  184. <figure>
  185. <img src="http://alistapart.com/d/415/fig1_menu.png" alt="Comparing the initial menu bar layout for fewer than six items with the layout for six or more items"/>
  186. </figure>
  187. <p>So, there you have it: squid ahoy! You can now add quantity as a styling condition to your repertoire.</p>
  188. <h2>Content-independent design</h2>
  189. <p>Responsive web design solves an important problem: it makes the <em>same</em> content comfortably digestible between <em>different</em> 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.”</p>
  190. <p>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.</p>
  191. <p>Web design is about mutability, difference, uncertainty. It’s about <em>not knowing</em>. Uniquely, it is a mode of visual design not about manifesting a form, but about anticipating the different forms something <em>might</em> 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.</p>