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.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="en">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Modern CSS For Dynamic Component-Based Architecture (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>Modern CSS For Dynamic Component-Based Architecture</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-04-18
  72. </p>
  73. </nav>
  74. <hr>
  75. <p>The language of CSS has had an explosion of new features and improvements in the last few years. As a result, feature parity between browsers is at an all-time high, and <a href="https://webkit.org/blog/13706/interop-2023/">efforts are being made</a> to continue releasing features consistently and synchronously among evergreen browsers.</p>
  76. <p>Today, we will explore modern project architecture, emphasizing theming, responsive layouts, and component design. We'll learn about features to improve code organization and dig into layout techniques such as grid and container queries. Finally, we'll review real-world examples of context-aware components that use cutting-edge CSS techniques. You're sure to be inspired to expand your CSS skills and ready to create scalable, future-friendly web projects.</p>
  77. <p>Since the early days of CSS, a convention to tame cross-browser styling inconsistencies has been the CSS reset. This refers to a group of rules that do things like to remove default spacing attributes or enforce inheritance of font styles. It has also grown more flexible in definition, and some folks use it as a place to put baseline global style overrides.</p>
  78. <p>Here are a few handy rules I now place in my reset to take advantage of modern CSS features. A wonderful thing about these rules is they are also progressive enhancements that don't strictly require fallbacks. If they are supported in a browser and are applied, great! And if not, there's no or minimal impact on the user experience.</p>
  79. <p>I set a common baseline for default links, which are scoped to those without a class. This is an assumption that classless links are intended to keep a regular, underlined link appearance. The update is to set the underline to use a relative thickness and increase the underline offset. The visual outcome may be minor, but it can improve the legibility of links, especially when presented in a list or other close-proximity contexts.</p>
  80. <pre class="language-css"><code class="language-css">
  81. <span class="token selector">a:not([class])</span> <span class="token punctuation">{</span>
  82. <span class="token property">text-decoration-thickness</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>0.08em<span class="token punctuation">,</span> 1px<span class="token punctuation">)</span><span class="token punctuation">;</span>
  83. <span class="token property">text-underline-offset</span><span class="token punctuation">:</span> 0.15em<span class="token punctuation">;</span>
  84. <span class="token punctuation">}</span></code></pre>
  85. <p>The <code>max()</code> function asks the browser to choose the larger of the presented options, which effectively ensures that in this rule, the underline cannot be thinner than <code>1px</code>.</p>
  86. <p>An exciting cross-browser update as of March 2022 was a switch of the default focus behavior for interactive elements to use <code>:focus-visible</code> by default. Whereas the <code>:focus</code> state applies no matter how an element receives focus, <code>:focus-visible</code> only produces a visible focus state based on the heuristics of the user's input modality. Practically speaking, this means that typically mouse users will not see a visible focus for elements like links or buttons, but a keyboard user who accesses those elements through tabbing will see a visible focus style.</p>
  87. <p>As for our reset, this means our visible focus styles will be attached to only the <code>:focus-visible</code> state.</p>
  88. <pre class="language-css"><code class="language-css"><span class="token selector">:focus-visible</span> <span class="token punctuation">{</span>
  89. <span class="token property">--outline-size</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>2px<span class="token punctuation">,</span> 0.15em<span class="token punctuation">)</span><span class="token punctuation">;</span>
  90. <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--outline-width<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--outline-size<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--outline-style<span class="token punctuation">,</span> solid<span class="token punctuation">)</span>
  91. <span class="token function">var</span><span class="token punctuation">(</span>--outline-color<span class="token punctuation">,</span> currentColor<span class="token punctuation">)</span><span class="token punctuation">;</span>
  92. <span class="token property">outline-offset</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--outline-offset<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--outline-size<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  93. <span class="token punctuation">}</span></code></pre>
  94. <p>In this rule, custom properties are used to set the various outline attributes. This allows the creation of a common baseline for our application's focus styles while allowing overrides for components as needed.</p>
  95. <p>You might also be less familiar with the <code>outline-offset</code> property, which defines the distance between the element and the outline. This property can use a negative value to inset the outline and place it inside the element. I often do this override for button component styles to ensure the outlines retain accessible contrast against the element.</p>
  96. <blockquote>
  97. <p>I’ve written about <a href="https://css-tricks.com/standardizing-focus-styles-with-css-custom-properties/">this outline technique</a> before if you’d like to learn more.</p>
  98. </blockquote>
  99. <p>The last two additions to my reset involve improving the scroll position for targeted or focused elements.</p>
  100. <p>Using <code>scroll-margin</code> properties, you can adjust the scroll position in relation to elements. The "margin" space does not affect the layout, just the offset of the scroll position.</p>
  101. <p>In this rule, the <code>:target</code> selector matches when an element is a target of an anchor link, also known as a "document fragment." The <code>scroll-margin-block-start</code> will allow for room between the target and the top of the viewport.</p>
  102. <pre class="language-css"><code class="language-css">
  103. <span class="token selector">:target</span> <span class="token punctuation">{</span>
  104. <span class="token property">scroll-margin-block-start</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span>
  105. <span class="token punctuation">}</span></code></pre>
  106. <p>The use of <code>scroll-margin-block-end</code> in this next rule allows for room between a focused element and the bottom of the viewport, which helps with tracking visible focus position.</p>
  107. <pre class="language-css"><code class="language-css">
  108. <span class="token selector">:focus</span> <span class="token punctuation">{</span>
  109. <span class="token property">scroll-margin-block-end</span><span class="token punctuation">:</span> 8vh<span class="token punctuation">;</span>
  110. <span class="token punctuation">}</span></code></pre>
  111. <p>Values for both rules can be adjusted to work best with your application layout. Consider that you might need a little bit of help from JavaScript if you need to <a href="https://www.tpgi.com/prevent-focused-elements-from-being-obscured-by-sticky-headers/#an-alternative-approach">account for sticky headers</a> or footers.</p>
  112. <p>Next up are two features with the potential to strongly impact your project architecture: nesting and cascade layers.</p>
  113. <p>Native CSS nesting began to be supported in Chromium 112, Safari 16.5, and very newly in Firefox Nightly so stable support should be shortly behind.</p>
  114. <p>For those who have used a preprocessor like Sass or LESS, native nesting will be familiar, but it does have some unique rules.</p>
  115. <p>A nested rule must begin with a symbol, meaning you cannot use an element selector by itself. But the ampersand - <code>&amp;</code> - character is also available and refers to the top-level selector, so that is one way to begin a nested selector. This condition may change as browser engineers and CSSWG members continue troubleshooting how to address <a href="https://www.w3.org/TR/css-nesting-1/#nesting">restrictions on nested rule selectors</a>.</p>
  116. <pre class="language-css"><code class="language-css">
  117. <span class="token selector">.my-element</span> <span class="token punctuation">{</span>
  118. <span class="token selector">a</span> <span class="token punctuation">{</span>
  119. <span class="token punctuation">}</span>
  120. <span class="token punctuation">}</span>
  121. <span class="token selector">.my-element</span> <span class="token punctuation">{</span>
  122. <span class="token selector">&amp; a</span> <span class="token punctuation">{</span>
  123. <span class="token punctuation">}</span>
  124. <span class="token punctuation">}</span></code></pre>
  125. <p>Alternatively, selectors such as <code>:is()</code> or <code>:where()</code> can begin a nested rule since they meet the “symbol” requirement. And standard class or attribute selection is also allowed, as well as the other combinators.</p>
  126. <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span>
  127. <span class="token selector">:is(a, button)</span> <span class="token punctuation">{</span>
  128. <span class="token punctuation">}</span>
  129. <span class="token selector">.button</span> <span class="token punctuation">{</span>
  130. <span class="token punctuation">}</span>
  131. <span class="token selector">[data-type]</span> <span class="token punctuation">{</span>
  132. <span class="token punctuation">}</span>
  133. <span class="token selector">+ .another-element</span> <span class="token punctuation">{</span>
  134. <span class="token punctuation">}</span>
  135. <span class="token punctuation">}</span></code></pre>
  136. <p>A possible gotcha with nesting selectors is that the compound result creates descendent selectors. In other words, a space character is added between the top-level selector and the nested selector. When you intend to have the nested selector be appended to the top-level selector, the use of the <code>&amp;</code> enables that result.</p>
  137. <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span>
  138. <span class="token selector">[data-type]</span> <span class="token punctuation">{</span>
  139. <span class="token punctuation">}</span>
  140. <span class="token selector">&amp;[data-type]</span> <span class="token punctuation">{</span>
  141. <span class="token punctuation">}</span>
  142. <span class="token punctuation">}</span>
  143. <span class="token selector">.my-element [data-type]</span> <span class="token punctuation">{</span>
  144. <span class="token punctuation">}</span>
  145. <span class="token selector">.my-element[data-type]</span> <span class="token punctuation">{</span>
  146. <span class="token punctuation">}</span></code></pre>
  147. <p>Use of <code>&amp;</code> also allows nested selectors for pseudo-elements and pseudo-classes.</p>
  148. <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span>
  149. <span class="token selector">&amp;::before</span> <span class="token punctuation">{</span>
  150. <span class="token punctuation">}</span>
  151. <span class="token selector">&amp;:hover</span> <span class="token punctuation">{</span>
  152. <span class="token punctuation">}</span>
  153. <span class="token punctuation">}</span></code></pre>
  154. <p>Review more examples of valid and invalid nesting rules from <a href="https://webkit.org/blog/13813/try-css-nesting-today-in-safari-technology-preview/">Jen Simmons</a> and <a href="https://developer.chrome.com/articles/css-nesting/">Adam Argyle</a>.</p>
  155. <p>You can safely begin using nesting today without Sass or LESS by incorporating a build tool such as <a href="https://lightningcss.dev/">LightningCSS</a>, which will pre-combine the selectors for your final stylesheet based on your browser targets.</p>
  156. <p>In a coordinated cross-browser rollout, the new at-rule of <code>@layer</code> became available as of Chromium 99, Safari 15.4, and Firefox 97 in early 2022. This at-rule is how to manage CSS cascade layers, which allows authors more control over two key features of the "C" in CSS: <em>specificity</em> and <em>order of appearance</em>. This is significant because those are the last two determining factors a browser considers when applying an element's style.</p>
  157. <p>Using <code>@layer</code>, we can define groups of rule sets with a pre-determined order to reduce the likelihood of conflicts. Being able to assign this order largely prevents the need to use <code>!important</code> and enables easier overrides of inherited styles from third-party or framework stylesheets.</p>
  158. <p>The critical rules to understand about cascade layers are:</p>
  159. <ul>
  160. <li>the initial order of layers defines the applied priority order
  161. <ul>
  162. <li>priority increases in order</li>
  163. <li>ex. first layer has less priority than the last layer</li>
  164. </ul>
  165. </li>
  166. <li>less-nested layered styles have priority over deeper nested layer styles</li>
  167. <li>un-layered styles have the highest priority over layered styles</li>
  168. </ul>
  169. <p>In this example, the initial layer order is given as <code>global</code> followed by <code>typography</code>. However, the styles added to those layers are written so that the <code>typography</code> layer is listed first. But, the <code>p</code> will be <code>blue</code> since that style is defined in the <code>typography</code> layer, and the initial layer order defines the typography layer later than the global layer.</p>
  170. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> global<span class="token punctuation">,</span> typography<span class="token punctuation">;</span></span>
  171. <span class="token selector">p</span> <span class="token punctuation">{</span>
  172. <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span>
  173. <span class="token punctuation">}</span>
  174. <span class="token atrule"><span class="token rule">@layer</span> typography</span> <span class="token punctuation">{</span>
  175. <span class="token selector">p</span> <span class="token punctuation">{</span>
  176. <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span>
  177. <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  178. <span class="token punctuation">}</span>
  179. <span class="token atrule"><span class="token rule">@layer</span> colors</span> <span class="token punctuation">{</span>
  180. <span class="token selector">p</span> <span class="token punctuation">{</span>
  181. <span class="token property">color</span><span class="token punctuation">:</span> pink<span class="token punctuation">;</span>
  182. <span class="token punctuation">}</span>
  183. <span class="token punctuation">}</span>
  184. <span class="token punctuation">}</span>
  185. <span class="token atrule"><span class="token rule">@layer</span> global</span> <span class="token punctuation">{</span>
  186. <span class="token selector">p</span> <span class="token punctuation">{</span>
  187. <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>245 30% 30%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  188. <span class="token punctuation">}</span>
  189. <span class="token punctuation">}</span></code></pre>
  190. <p>The nested layer of <code>color</code> within <code>typography</code> also has lower-priority than the un-nested style. Finally, the paragraph will also have a <code>margin-bottom</code> of <code>2rem</code> since the un-layered style has higher priority over the layered styles.</p>
  191. <blockquote>
  192. <p>Learn more in my <a href="https://www.smashingmagazine.com/2022/01/introduction-css-cascade-layers/">guide to cascade layers</a>, and watch <a href="https://www.youtube.com/watch?v=zEPXyqj7pEA">Bramus Van Damme’s talk from CSS Day 2022</a>.</p>
  193. </blockquote>
  194. <p>As with many newer features, there is much room for experimentation, and "best practices" or "standards of use" have not been established. Decisions like whether to include cascade layers, what to name them, and how to order them will be very project dependent.</p>
  195. <p>Here’s a layer order I have been trying out in my own projects:</p>
  196. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> reset<span class="token punctuation">,</span> theme<span class="token punctuation">,</span> global<span class="token punctuation">,</span> layout<span class="token punctuation">,</span> components<span class="token punctuation">,</span> utilities<span class="token punctuation">,</span> states<span class="token punctuation">;</span></span></code></pre>
  197. <p>Miriam Suzanne, the spec author for cascade layers, describes a few contexts and other <a href="https://12daysofweb.dev/2022/cascade-layers/">considerations for naming and ordering layers</a>.</p>
  198. <p>Moving to cascade layers is a bit tricky, although a polyfill is available. However, at-rules cannot be detected by <code>@supports</code> in CSS. Even if they could, there's still the issue that un-layered styles that you may not be ready to move to layers would continue to override layer styles.</p>
  199. <p>The desire to detect <code>@layer</code> support and minimize the conflict between layered and un-layered styles was a motivating factor in creating my project <a href="https://supportscss.dev/">SupportsCSS</a>, a feature detection script. It adds classes to <code>&lt;html&gt;</code> to indicate support or lack thereof, which can then be used as part of your progressive enhancement strategy for many modern CSS features, including cascade layers.</p>
  200. <p>There are three features I immediately begin using when starting a new project, large or small. The first is custom properties, also known as CSS variables.</p>
  201. <p>The <a href="https://almanac.httparchive.org/en/2022/css#custom-properties">2022 Web Almanac</a> - which sources data from the HTTP Archive dataset and included 8.36M websites - noted that 43% of pages are using custom properties and have at least one <code>var()</code> function. My prediction is that number will continue to grow dramatically now that Internet Explorer 11 has reached end-of-life, as lack of IE11 support prevented many teams from picking up custom properties.</p>
  202. <p>The Almanac results also showed that the ruling type used by custom property values was color, and that is in fact how we’ll begin using them as well.</p>
  203. <p>For the remainder of the examples, we’ll be building up components and branding for our imaginary product Jaberwocky.</p>
  204. <p><img src="https://moderncss.dev/img/posts/32/brand.png" alt=""></p>
  205. <p>We’ll begin by placing the brand colors as custom properties within the <code>:root</code> selector, within our <code>theme</code> layer.</p>
  206. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> theme</span> <span class="token punctuation">{</span>
  207. <span class="token selector">:root</span> <span class="token punctuation">{</span>
  208. <span class="token property">--primary</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>265<span class="token punctuation">,</span> 38%<span class="token punctuation">,</span> 13%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  209. <span class="token property">--secondary</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>283<span class="token punctuation">,</span> 6%<span class="token punctuation">,</span> 45%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  210. <span class="token property">--tertiary</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>257<span class="token punctuation">,</span> 15%<span class="token punctuation">,</span> 91%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  211. <span class="token property">--light</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>270<span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 99%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  212. <span class="token property">--accent</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>278<span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 92%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  213. <span class="token property">--accent--alt</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>279<span class="token punctuation">,</span> 100%<span class="token punctuation">,</span> 97%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  214. <span class="token property">--accent--ui</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>284<span class="token punctuation">,</span> 55%<span class="token punctuation">,</span> 66%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  215. <span class="token punctuation">}</span>
  216. <span class="token punctuation">}</span></code></pre>
  217. <p>You may also wish to place font sizes or other "tokens" you anticipate re-using in this theme layer. Later, we'll elevate some component properties to this global space. We'll also continue to inject custom properties throughout our layout utilities and component styles to develop an API for them.</p>
  218. <p>Now that we have a brand and color palette, it's time to add the other two features.</p>
  219. <p>First is <code>color-scheme</code>, which allows us to inform the browser whether the default site appearance is <code>light</code> or <code>dark</code> or assign a priority if both are supported. The priority comes from the order the values are listed, so <code>light dark</code> gives "light" priority. The use of <code>color-scheme</code> may affect the color of scrollbars and adjust the appearance of input fields. Unless you provide overrides, it can also adjust the <code>background</code> and <code>color</code> properties. While we are setting it on <code>html</code>, you may also localize it to a certain component or section of a layout. Sara Joy shares more about <a href="https://www.htmhell.dev/adventcalendar/2022/19/">how color-scheme works</a>.</p>
  220. <p>The second property is <code>accent-color</code> which applies your selected color to the form inputs of checkboxes, radio buttons, range, and progress elements. For radio buttons and checkboxes, this means it's used to color the input in the <code>:checked</code> state. This is an impactful step towards theming these tricky-to-style form inputs and may be a sufficient solution instead of completely restyling. Michelle Barker shares more on <a href="https://www.smashingmagazine.com/2021/09/simplifying-form-styles-accent-color/">how accent-color works</a>.</p>
  221. <blockquote>
  222. <p>If you do feel you need to have full style control, see my guides to <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">styling radio buttons</a> and <a href="https://moderncss.dev/pure-css-custom-checkbox-style/">styling checkboxes</a>.</p>
  223. </blockquote>
  224. <p>Jaberwocky best supports a <code>light</code> appearance, and will use the darkest purple that is assigned to <code>--accent--ui</code> for the <code>accent-color</code>.</p>
  225. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> theme</span> <span class="token punctuation">{</span>
  226. <span class="token selector">html</span> <span class="token punctuation">{</span>
  227. <span class="token property">color-scheme</span><span class="token punctuation">:</span> light<span class="token punctuation">;</span>
  228. <span class="token property">accent-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--accent--ui<span class="token punctuation">)</span><span class="token punctuation">;</span>
  229. <span class="token punctuation">}</span>
  230. <span class="token punctuation">}</span></code></pre>
  231. <p>There is so much we could cover regarding CSS layout, but I want to share two utilities I use in nearly every project for creating responsive grids. The first solution relies on CSS grid, and the second on flexbox.</p>
  232. <p>Using CSS grid, this first utility creates a responsive set of columns that are auto-generated depending on the amount of available inline space.</p>
  233. <p>Beyond defining <code>display: grid</code>, the magic of this rule is in the assignment for <code>grid-template-columns</code> which uses the <code>repeat()</code> function.</p>
  234. <details open>
  235. <summary>CSS for "CSS Grid Layout"</summary>
  236. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  237. <span class="token selector">.layout-grid</span> <span class="token punctuation">{</span>
  238. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  239. <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>
  240. auto-fit<span class="token punctuation">,</span>
  241. <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token function">min</span><span class="token punctuation">(</span>100%<span class="token punctuation">,</span> 30ch<span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span>
  242. <span class="token punctuation">)</span><span class="token punctuation">;</span>
  243. <span class="token punctuation">}</span>
  244. <span class="token punctuation">}</span>
  245. </code></pre>
  246. </details>
  247. <div class="demo">
  248. <div class="demo--content">
  249. <p class="layout-grid-5"><span>Item 1</span><span>Item 2</span><span>Item 3</span><span>Item 4</span><span>Item 5</span></p>
  250. </div>
  251. </div>
  252. <p>The first parameter within repeat uses the <code>auto-fit</code> keyword, which tells grid to create as many columns as can fit given the sizing definition which follows. The sizing definition uses the grid-specific function of <code>minmax()</code>, which accepts two values that list the minimum and maximum allowed size for the column. For the maximum, we've used <code>1fr</code>, which will allow the columns to stretch out and share the space equitably when more than the minimum is available.</p>
  253. <p>For the minimum, we've included the extra CSS math function of <code>min()</code> to ask the browser to use the smaller computed size between the listed options. The reason is that there is potential for overflow once the available space is more narrow than <code>30ch</code>. By listing <code>100%</code> as an alternate option, the column can fill whatever space is available below that minimum.</p>
  254. <p>The behavior with this minimum in place means that once the available space becomes less than the amount required for multiple elements to fit in the row, the elements will drop to create new rows. So with a minimum of <code>30ch</code>, we can fit at least three elements in a <code>100ch</code> space. However, if that space reduces to <code>70ch</code>, then only two would fit in one row, and one would drop to a new row.</p>
  255. <p>To improve customization, we'll drop in a custom property to define the minimum allowed size for a column, which will function as a "breakpoint" for each column before causing the overflow to become new rows. For the most flexibility, I also like to include a custom property to allow overriding the <code>gap</code>.</p>
  256. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  257. <span class="token selector">.layout-grid</span> <span class="token punctuation">{</span>
  258. <span class="token property">--layout-grid-min</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span>
  259. <span class="token property">--layout-grid-gap</span><span class="token punctuation">:</span> 3vw<span class="token punctuation">;</span>
  260. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  261. <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>
  262. auto-fit<span class="token punctuation">,</span>
  263. <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token function">min</span><span class="token punctuation">(</span>100%<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-grid-min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span>
  264. <span class="token punctuation">)</span><span class="token punctuation">;</span>
  265. <span class="token property">gap</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-grid-gap<span class="token punctuation">)</span><span class="token punctuation">;</span>
  266. <span class="token punctuation">}</span>
  267. <span class="token punctuation">}</span></code></pre>
  268. <p>Since this solution uses CSS grid, the grid children are destined to stay in a grid formation. Items that drop to create new rows will remain constrained within the implicit columns formed on the prior rows.</p>
  269. <p>Sometimes in a grid with an odd number of children, you may want to allow them to expand and fill any leftover space. For that behavior, we switch our strategy to use flexbox.</p>
  270. <p>The flexbox grid utility shares two common features with the CSS grid utility: defining a minimum "column" size and the <code>gap</code> size. We can set up two global custom properties to keep those initial values in sync. We'll elevate those defaults to our <code>theme</code> layer.</p>
  271. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> theme</span> <span class="token punctuation">{</span>
  272. <span class="token selector">:root</span> <span class="token punctuation">{</span>
  273. <span class="token property">--layout-column-min</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span>
  274. <span class="token property">--layout-gap</span><span class="token punctuation">:</span> 3vmax<span class="token punctuation">;</span>
  275. <span class="token punctuation">}</span>
  276. <span class="token punctuation">}</span></code></pre>
  277. <p>Then in the grid utility and to kick off our flexbox utility, we’ll use those globals as the defaults for the local custom properties.</p>
  278. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  279. <span class="token selector">.layout-grid</span> <span class="token punctuation">{</span>
  280. <span class="token property">--layout-grid-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-column-min<span class="token punctuation">)</span><span class="token punctuation">;</span>
  281. <span class="token property">--layout-grid-gap</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-gap<span class="token punctuation">)</span><span class="token punctuation">;</span>
  282. <span class="token punctuation">}</span>
  283. <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span>
  284. <span class="token property">--flex-grid-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-column-min<span class="token punctuation">)</span><span class="token punctuation">;</span>
  285. <span class="token property">--flex-grid-gap</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-gap<span class="token punctuation">)</span><span class="token punctuation">;</span>
  286. <span class="token property">gap</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--flex-grid-gap<span class="token punctuation">)</span><span class="token punctuation">;</span>
  287. <span class="token punctuation">}</span>
  288. <span class="token punctuation">}</span></code></pre>
  289. <p>Beyond those custom properties, the base flexbox grid utility simply sets up the display and wrap properties. Wrapping is important so that elements can drop and create new rows as space decreases.</p>
  290. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  291. <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span>
  292. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  293. <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span>
  294. <span class="token punctuation">}</span>
  295. <span class="token punctuation">}</span></code></pre>
  296. <p>With CSS grid, the parent controls the child size. But with flexbox, the children control their sizing. Since our utility doesn't know what the flexbox children will be, we'll use the universal selector - <code>*</code> - to select all direct children to apply flexbox sizing. With the <code>flex</code> shorthand, we define that children can grow and shrink and set the <code>flex-basis</code> to the minimum value.</p>
  297. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  298. <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span>
  299. <span class="token selector">&gt; *</span> <span class="token punctuation">{</span>
  300. <span class="token property">flex</span><span class="token punctuation">:</span> 1 1 <span class="token function">var</span><span class="token punctuation">(</span>--flex-grid-min<span class="token punctuation">)</span><span class="token punctuation">;</span>
  301. <span class="token punctuation">}</span>
  302. <span class="token punctuation">}</span>
  303. <span class="token punctuation">}</span></code></pre>
  304. <p>As with the previous grid utility, this “min” value will cause elements to wrap to new rows once the available space is reduced. The difference is that the <code>flex-grow</code> behavior will allow children to grow into unused space within the row. Given a grid of three where only two elements can fit in a row, the third will expand to fill the entire second row. And in a grid of five where three elements can align, the remaining two will share the space of the second row.</p>
  305. <details open>
  306. <summary>CSS for "CSS Flexbox Grid Layout"</summary>
  307. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  308. <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span>
  309. <span class="token property">--flex-grid-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-column-min<span class="token punctuation">)</span><span class="token punctuation">;</span>
  310. <span class="token property">--flex-grid-gap</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-gap<span class="token punctuation">)</span><span class="token punctuation">;</span>
  311. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  312. <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span>
  313. <span class="token selector">&gt; *</span> <span class="token punctuation">{</span>
  314. <span class="token property">flex</span><span class="token punctuation">:</span> 1 1 <span class="token function">var</span><span class="token punctuation">(</span>--flex-grid-min<span class="token punctuation">)</span><span class="token punctuation">;</span>
  315. <span class="token punctuation">}</span>
  316. <span class="token punctuation">}</span>
  317. <span class="token punctuation">}</span>
  318. </code></pre>
  319. </details>
  320. <div class="demo">
  321. <div class="demo--content">
  322. <p class="flex-layout-grid-679"><span>Item 1</span><span>Item 2</span><span>Item 3</span><span>Item 4</span><span>Item 5</span></p>
  323. </div>
  324. </div>
  325. <div class="heading-wrapper h3">
  326. <h3 id="prepare-for-container-queries">Prepare for Container Queries</h3>
  327. <a class="anchor" href="#prepare-for-container-queries" aria-labelledby="prepare-for-container-queries"><span hidden>#</span></a></div>
  328. <p>Shortly, we will use container size queries to develop several component styles. Container size queries allow developing rules that change elements based on available space.</p>
  329. <p>To correctly query against the size of flexbox or grid children, we can enhance our utilities to include container definitions.</p>
  330. <p>We’ll default the container name to <code>grid-item</code> while also allowing an override via a custom property. This allows specific container query instances to be explicit about which container they are querying against.</p>
  331. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> layout</span> <span class="token punctuation">{</span>
  332. <span class="token selector">:is(.layout-grid, .flex-layout-grid) &gt; *</span> <span class="token punctuation">{</span>
  333. <span class="token property">container</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--grid-item-container<span class="token punctuation">,</span> grid-item<span class="token punctuation">)</span> / inline-size<span class="token punctuation">;</span>
  334. <span class="token punctuation">}</span>
  335. <span class="token punctuation">}</span></code></pre>
  336. <p>Later examples will demonstrate how to use features of container size queries and make use of these layout utility containers.</p>
  337. <blockquote>
  338. <p><strong>Note</strong>: There is <a href="https://bugs.webkit.org/show_bug.cgi?id=256047">a bug as of Safari 16.4</a> where using containment on a grid using <code>auto-fit</code> collapses widths to zero, so proceed with caution if you use this strategy before the bug is resolved.</p>
  339. </blockquote>
  340. <p>We’ve reached the first of four components we’ll develop to showcase even more modern CSS features. While you won’t have a complete framework after four components, you will have a solid foundation to continue building from and some shiny new things in your CSS toolbox!</p>
  341. <p><img src="https://moderncss.dev/img/posts/32/preview-buttons.png" alt=""></p>
  342. <p>Our styles will support the following variations of a button:</p>
  343. <ul>
  344. <li>a button element</li>
  345. <li>a link element</li>
  346. <li>text plus an icon</li>
  347. <li>icon plus text</li>
  348. <li>icon-only</li>
  349. </ul>
  350. <p>There are some reset properties beyond the scope of this article, but the first properties that make a difference in customizing our buttons have to do with color.</p>
  351. <div class="heading-wrapper h3">
  352. <h3 id="custom-property-and-component-apis">Custom Property and Component APIs</h3>
  353. <a class="anchor" href="#custom-property-and-component-apis" aria-labelledby="custom-property-and-component-apis"><span hidden>#</span></a></div>
  354. <p>For both the <code>color</code> and <code>background-color</code> properties, we’ll begin to develop an API for our buttons by leveraging custom properties.</p>
  355. <p>The API is created by first assigning an undefined custom property. Later, we can tap into that API to easily create button variants, including when adjusting for states like <code>:hover</code> or <code>:disabled</code>.</p>
  356. <p>Then, we use the values that will signify the "default" variant for the second value, which is considered the property's fallback. In this case, our lavender <code>--accent</code> property will be the default color. Our <code>--primary</code> for this theme is nearly black, and will be the complimenting default for the <code>color</code> property.</p>
  357. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@layer</span> components</span> <span class="token punctuation">{</span>
  358. <span class="token selector">.button</span> <span class="token punctuation">{</span>
  359. <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-color<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  360. <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-bg<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--accent<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  361. <span class="token punctuation">}</span>
  362. <span class="token punctuation">}</span></code></pre>
  363. <div class="heading-wrapper h3">
  364. <h3 id="creating-variant-styles-with-has">Creating Variant Styles with <code>:has()</code></h3>
  365. <a class="anchor" href="#creating-variant-styles-with-has" aria-labelledby="creating-variant-styles-with-has"><span hidden>#</span></a></div>
  366. <p>Next, we’ll address the presence of an <code>.icon</code> within the button. Detecting presence is a special capability of the very modern feature <code>:has()</code>.</p>
  367. <p>With <code>:has()</code>, we can look inside the button and see whether it has an <code>.icon</code> and if it does, update the button’s properties. In this case, applying flex alignment and a <code>gap</code> value. Because appending the <code>:has()</code> pseudo class will increase the specificity of the base class selector, we’ll also wrap the <code>:has()</code> clause with <code>:where()</code> to null the specificity of the clause to zero. Meaning, the selector will retain the specificity of a class only.</p>
  368. <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:has(.icon))</span> <span class="token punctuation">{</span>
  369. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  370. <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span>
  371. <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  372. <span class="token punctuation">}</span></code></pre>
  373. <p>In our markup for the case of the icon-only buttons is an element with the class of <code>.inclusively-hidden</code>, which removes the visible label but still allows an accessible label for assistive technology like screen readers. So, we can look for that class to signify the icon-only variation and produce a circle appearance.</p>
  374. <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:has(.inclusively-hidden))</span> <span class="token punctuation">{</span>
  375. <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
  376. <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span>
  377. <span class="token punctuation">}</span></code></pre>
  378. <p>Next, for buttons without icons, we want to set a minimum inline size, and center the text. We can achieve this by combining the <code>:not()</code> pseudo-class with <code>:has()</code> to create a selector that says “buttons that do not have icons.”</p>
  379. <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:not(:has(.icon)))</span> <span class="token punctuation">{</span>
  380. <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  381. <span class="token property">min-inline-size</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span>
  382. <span class="token punctuation">}</span></code></pre>
  383. <p>Our final essential button variation is the case of buttons that are not icon-only. This means text buttons and those that include an icon. So, our selector will again combine <code>:not()</code> and <code>:has()</code> to say “buttons that do not have the hidden class,” which we noted was the signifier for the icon-only variant.</p>
  384. <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:not(:has(.inclusively-hidden)))</span> <span class="token punctuation">{</span>
  385. <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-padding<span class="token punctuation">,</span> 0.75em 1em<span class="token punctuation">)</span><span class="token punctuation">;</span>
  386. <span class="token property">border-radius</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  387. <span class="token punctuation">}</span></code></pre>
  388. <p>This variant exposes a <code>--button-padding</code> custom property, and sets an explicit <code>border-radius</code>.</p>
  389. <details open>
  390. <summary>CSS for "Button Component"</summary>
  391. <pre class="language-css"><code class="language-css"><span class="token selector">.button</span> <span class="token punctuation">{</span>
  392. <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-color<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  393. <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-bg<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--accent<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  394. <span class="token punctuation">}</span>
  395. <span class="token selector">.button:where(:has(.icon))</span> <span class="token punctuation">{</span>
  396. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  397. <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span>
  398. <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  399. <span class="token punctuation">}</span>
  400. <span class="token selector">.button:where(:has(.inclusively-hidden))</span> <span class="token punctuation">{</span>
  401. <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span>
  402. <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span>
  403. <span class="token punctuation">}</span>
  404. <span class="token selector">.button:where(:not(:has(.icon)))</span> <span class="token punctuation">{</span>
  405. <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  406. <span class="token property">min-inline-size</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span>
  407. <span class="token punctuation">}</span>
  408. <span class="token selector">.button:where(:not(:has(.inclusively-hidden)))</span> <span class="token punctuation">{</span>
  409. <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-padding<span class="token punctuation">,</span> 0.35em 1em<span class="token punctuation">)</span><span class="token punctuation">;</span>
  410. <span class="token property">border-radius</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  411. <span class="token punctuation">}</span>
  412. </code></pre>
  413. </details>
  414. <div class="demo no-resize">
  415. <div class="demo--content">
  416. <div class="demo-container-136">
  417. <button type="button" class="btn-136">Button</button>
  418. <a href="" class="btn-136">Link</a>
  419. <button type="button" class="btn-136">
  420. Text + Icon Button
  421. <svg class="icon-136" aria-hidden="true">
  422. <use href="#star"></use>
  423. </svg>
  424. </button>
  425. <button type="button" class="btn-136">
  426. <svg class="icon-136" aria-hidden="true">
  427. <use href="#star"></use>
  428. </svg>
  429. Icon + Text button
  430. </button>
  431. <button type="button" class="btn-136">
  432. <svg class="icon-136" aria-hidden="true">
  433. <use href="#star"></use>
  434. </svg>
  435. <span class="inclusively-hidden-136">Icon only button</span>
  436. </button>
  437. </div>
  438. </div>
  439. </div>
  440. <p><svg hidden>
  441. <defs>
  442. <symbol viewbox="0 0 256 256" id="star">
  443. <path d="m234 115.5l-45.2 37.6l14.3 58.1a16.5 16.5 0 0 1-15.8 20.8a16.1 16.1 0 0 1-8.7-2.6l-50.5-31.9h-.2L81 227.2a18 18 0 0 1-20.1-.6a18.5 18.5 0 0 1-7-19.6l13.5-53.1L22 115.5a16.8 16.8 0 0 1-5.2-18.1A16.5 16.5 0 0 1 31.4 86l59-3.8l22.4-55.8A16.4 16.4 0 0 1 128 16a16.4 16.4 0 0 1 15.2 10.4l22 55.5l59.4 4.1a16.4 16.4 0 0 1 14.6 11.4a16.8 16.8 0 0 1-5.2 18.1Z"></path>
  444. </symbol>
  445. </defs>
  446. </svg></p>
  447. <div class="heading-wrapper h3">
  448. <h3 id="using-custom-properties-api-for-states">Using Custom Properties API for States</h3>
  449. <a class="anchor" href="#using-custom-properties-api-for-states" aria-labelledby="using-custom-properties-api-for-states"><span hidden>#</span></a></div>
  450. <p>While the initial visual appearance is complete, we need to handle for two states: <code>:hover</code> and <code>:focus-visible</code>. Here is where we get to use our custom properties API, with no additional properties required to make the desired changes.</p>
  451. <p>For the <code>:hover</code> state, we are updating the color properties. And for <code>:focus-visible</code>, we're tapping into the API we exposed for that state within our reset. Notably, we're using a negative <code>outline-offset</code> to place it inside the button boundary which helps with ensuring proper contrast.</p>
  452. <pre class="language-css"><code class="language-css"><span class="token selector">.button:hover</span> <span class="token punctuation">{</span>
  453. <span class="token property">--button-bg</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--accent--alt<span class="token punctuation">)</span><span class="token punctuation">;</span>
  454. <span class="token property">--button-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span><span class="token punctuation">;</span>
  455. <span class="token punctuation">}</span>
  456. <span class="token selector">.button:focus-visible</span> <span class="token punctuation">{</span>
  457. <span class="token property">--outline-style</span><span class="token punctuation">:</span> dashed<span class="token punctuation">;</span>
  458. <span class="token property">--outline-offset</span><span class="token punctuation">:</span> -0.35em<span class="token punctuation">;</span>
  459. <span class="token punctuation">}</span></code></pre>
  460. <p><img src="https://moderncss.dev/img/posts/32/preview-cards.png" alt=""></p>
  461. <p>For the card component, we have three variants and one state to manage:</p>
  462. <ul>
  463. <li>default, small card</li>
  464. <li>“new” style</li>
  465. <li>wide with larger text</li>
  466. <li>focus-visible state</li>
  467. </ul>
  468. <p>We’ll start from the baseline styles that provide the basic positioning and styles of the card elements. The styles don’t match our mocked-up design, but the cards are usable. And that’s pretty critical that our component generally “works” without the latest features! From this base, we can progressively enhance up to our ideal appearance.</p>
  469. <p>Here are a few other details about our cards and expected usage:</p>
  470. <ul>
  471. <li>we’ll place them within our flexbox-based layout grid</li>
  472. <li>the layout grid will be within a wrapping container</li>
  473. </ul>
  474. <p>The cards will anticipate the layout grid and it’s wrapper, which both have been defined as containers with distinct names. That means we can prepare container queries to further adjust the card layouts.</p>
  475. <details false>
  476. <summary>CSS for "Base Card Styles"</summary>
  477. <pre class="language-css"><code class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span>
  478. <span class="token property">--card-bg</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--demo-light<span class="token punctuation">)</span><span class="token punctuation">;</span>
  479. <span class="token property">--dot-color</span><span class="token punctuation">:</span> <span class="token function">color-mix</span><span class="token punctuation">(</span>in hsl<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--demo-primary<span class="token punctuation">)</span><span class="token punctuation">,</span> transparent 95%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  480. <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--card-bg<span class="token punctuation">)</span><span class="token punctuation">;</span>
  481. <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">radial-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--dot-color<span class="token punctuation">)</span> 10%<span class="token punctuation">,</span> transparent 12%<span class="token punctuation">)</span><span class="token punctuation">,</span>
  482. <span class="token function">radial-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--dot-color<span class="token punctuation">)</span> 11%<span class="token punctuation">,</span> transparent 13%<span class="token punctuation">)</span><span class="token punctuation">;</span>
  483. <span class="token property">background-size</span><span class="token punctuation">:</span> 28px 28px<span class="token punctuation">;</span>
  484. <span class="token property">background-position</span><span class="token punctuation">:</span> 0 0<span class="token punctuation">,</span> 72px 72px<span class="token punctuation">;</span>
  485. <span class="token property">padding</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span>
  486. <span class="token property">border</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--demo-primary<span class="token punctuation">)</span><span class="token punctuation">;</span>
  487. <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span>
  488. <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  489. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  490. <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span>
  491. <span class="token property">align-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>
  492. <span class="token punctuation">}</span>
  493. <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span>
  494. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  495. <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span>
  496. <span class="token punctuation">}</span>
  497. <span class="token selector">.card__number-icon::before</span> <span class="token punctuation">{</span>
  498. <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">"0"</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-num<span class="token punctuation">)</span><span class="token punctuation">;</span>
  499. <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--demo-accent<span class="token punctuation">)</span><span class="token punctuation">;</span>
  500. <span class="token property">font-weight</span><span class="token punctuation">:</span> 600<span class="token punctuation">;</span>
  501. <span class="token property">font-size</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span>
  502. <span class="token punctuation">}</span>
  503. <span class="token selector">.card__number-icon::before,
  504. .card__number-icon img</span> <span class="token punctuation">{</span>
  505. <span class="token property">width</span><span class="token punctuation">:</span> 2.25rem<span class="token punctuation">;</span>
  506. <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span>
  507. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  508. <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  509. <span class="token punctuation">}</span>
  510. <span class="token selector">.card__number-icon img</span> <span class="token punctuation">{</span>
  511. <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--demo-tertiary<span class="token punctuation">)</span><span class="token punctuation">;</span>
  512. <span class="token property">padding</span><span class="token punctuation">:</span> 0.15rem<span class="token punctuation">;</span>
  513. <span class="token punctuation">}</span>
  514. <span class="token selector">.card a</span> <span class="token punctuation">{</span>
  515. <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  516. <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--demo-primary<span class="token punctuation">)</span><span class="token punctuation">;</span>
  517. <span class="token punctuation">}</span>
  518. <span class="token selector">.card a::before</span> <span class="token punctuation">{</span>
  519. <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span>
  520. <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  521. <span class="token property">inset</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  522. <span class="token punctuation">}</span>
  523. <span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span>
  524. <span class="token property">font-weight</span><span class="token punctuation">:</span> 400<span class="token punctuation">;</span>
  525. <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span>
  526. <span class="token punctuation">}</span>
  527. <span class="token selector">.card a</span> <span class="token punctuation">{</span>
  528. <span class="token property">font-size</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span>
  529. <span class="token punctuation">}</span>
  530. </code></pre>
  531. </details>
  532. <div class="heading-wrapper h3">
  533. <h3 id="styling-based-on-element-presence">Styling Based on Element Presence</h3>
  534. <a class="anchor" href="#styling-based-on-element-presence" aria-labelledby="styling-based-on-element-presence"><span hidden>#</span></a></div>
  535. <p>Let’s start with the “New” card variation. There are two details that change, both based on the presence of the <code>.tag</code> element. The hint about how to handle these styles is that we’re detecting the presence of something, which means we’ll bring in <code>:has()</code> for the job.</p>
  536. <p>The first detail is to add an additional border to the card, which we’ll actually apply with a <code>box-shadow</code> because it will not add length to the card’s box model like a real border would. Also, the card already has a visible, actual border as part of it’s styling, which this variation will retain.</p>
  537. <pre class="language-css"><code class="language-css"><span class="token selector">.card:has(.tag)</span> <span class="token punctuation">{</span>
  538. <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 0 4px <span class="token function">var</span><span class="token punctuation">(</span>--accent<span class="token punctuation">)</span><span class="token punctuation">;</span>
  539. <span class="token punctuation">}</span></code></pre>
  540. <p>The other detail is to adjust the display of the headline, which the “New” tag resides in. This selector will be scoped to assume one of two header tags has been used. We’ll use <code>:is()</code> to efficiently create that group. And since we’ll be adding more headline styling soon, we’ll also try out nesting for this rule.</p>
  541. <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span>
  542. <span class="token selector">&amp;:has(.tag)</span> <span class="token punctuation">{</span>
  543. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  544. <span class="token property">gap</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span>
  545. <span class="token property">justify-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span>
  546. <span class="token punctuation">}</span>
  547. <span class="token punctuation">}</span></code></pre>
  548. <details false>
  549. <summary>CSS for "'New' Card"</summary>
  550. <pre class="language-css"><code class="language-css"><span class="token selector">.card:has(.tag)</span> <span class="token punctuation">{</span>
  551. <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 0 4px <span class="token function">var</span><span class="token punctuation">(</span>--demo-accent<span class="token punctuation">)</span><span class="token punctuation">;</span>
  552. <span class="token punctuation">}</span>
  553. <span class="token selector">.card :is(h2, h3):has(.tag)</span> <span class="token punctuation">{</span>
  554. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  555. <span class="token property">gap</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span>
  556. <span class="token property">justify-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span>
  557. <span class="token punctuation">}</span>
  558. </code></pre>
  559. </details>
  560. <p>Our baseline card styles include a method for making the card surface seem clickable even though the link element only wraps the headline text. But when the card link is focused, we want an outline to correctly appear near the perimeter of the card.</p>
  561. <p>We can achieve this without any positioning hackery by using the <code>:focus-within</code> pseudo-class. With <code>:focus-within</code>, we can style a parent element when a child is in a focused state. That let’s us add a regular <code>outline</code> to the card by providing a negative <code>outline-offset</code> to pull it inside the existing border.</p>
  562. <pre class="language-css"><code class="language-css"><span class="token selector">.card:focus-within</span> <span class="token punctuation">{</span>
  563. <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid #b77ad0<span class="token punctuation">;</span>
  564. <span class="token property">outline-offset</span><span class="token punctuation">:</span> -6px<span class="token punctuation">;</span>
  565. <span class="token punctuation">}</span></code></pre>
  566. <p>That still leaves us the default outline on the link, which we’ll switch to use a <code>transparent</code> outline. The reason is that we still need to retain the outline for focus visibility for users of forced-colors mode, which removes our defined colors and swaps to a limited palette. In that mode, <code>transparent</code> will be replaced with a solid, visible color.</p>
  567. <pre class="language-css"><code class="language-css"><span class="token selector">.card a:focus-visible</span> <span class="token punctuation">{</span>
  568. <span class="token property">--outline-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span>
  569. <span class="token punctuation">}</span></code></pre>
  570. <p>The final stateful style we’ll add is to include a text underline on the link when it is hovered or has visible focus. This helps identify the purpose as a link.</p>
  571. <pre class="language-css"><code class="language-css"><span class="token selector">.card a:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span>
  572. <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span>
  573. <span class="token punctuation">}</span></code></pre>
  574. <details false>
  575. <summary>CSS for "Card States"</summary>
  576. <pre class="language-css"><code class="language-css"><span class="token selector">.card:focus-within</span> <span class="token punctuation">{</span>
  577. <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid #b77ad0<span class="token punctuation">;</span>
  578. <span class="token property">outline-offset</span><span class="token punctuation">:</span> -6px<span class="token punctuation">;</span>
  579. <span class="token punctuation">}</span>
  580. <span class="token selector">.card a:is(:focus, :focus-visible)</span> <span class="token punctuation">{</span>
  581. <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid transparent<span class="token punctuation">;</span>
  582. <span class="token punctuation">}</span>
  583. <span class="token selector">.card a:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span>
  584. <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span>
  585. <span class="token punctuation">}</span>
  586. </code></pre>
  587. </details>
  588. <div class="heading-wrapper h3">
  589. <h3 id="context-based-container-queries">Context-Based Container Queries</h3>
  590. <a class="anchor" href="#context-based-container-queries" aria-labelledby="context-based-container-queries"><span hidden>#</span></a></div>
  591. <p>Since we’ve placed our demo cards in the flexbox layout grid, they already seem to be responsive. However, our design mockup included a “wide” card variation that is slightly different than simply stretching out the basic card.</p>
  592. <p>If you recall, we already defined each child of our flexbox grid to be a container. The default container name is <code>grid-item</code>. Additionally, there is a wrapper around the layout grid which also is defined as a container named <code>layout-container</code>. One level of our container queries will be in response to how wide the entire layout grid is, for which we’ll query the <code>layout-container</code>, and the other will respond to the inline size of a unique flex child, which is the <code>grid-item</code> container.</p>
  593. <p><img src="https://moderncss.dev/img/posts/32/spec-cards.png" alt=""></p>
  594. <p>A key concept is that a container query cannot style the container itself. That’s why we haven’t made the actual <code>.card</code> a container, but are looking to its direct ancestor of the <code>grid-item</code> container to attach the container query. The <code>grid-item</code> container will be equivalent to the inline-size of the card itself since it directly wraps the card.</p>
  595. <p>We can also use the new media range query syntax when using container size queries. This enables math operators like <code>&gt;</code> (greater than) to compare values.</p>
  596. <p>We’ll assign the “wide” variation styles when the <code>grid-item</code> container’s inline size is greater than <code>35ch</code>.</p>
  597. <pre class="language-css"><code class="language-css">
  598. <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size &gt; 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  599. <span class="token selector">.card</span> <span class="token punctuation">{</span>
  600. <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
  601. <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  602. <span class="token property">justify-content</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span>
  603. <span class="token property">gap</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span>
  604. <span class="token punctuation">}</span>
  605. <span class="token punctuation">}</span></code></pre>
  606. <p>The styles switch the grid orientation into columns instead of the default of rows, which places the number and icon container on the starting side. Then, we’ve added some alignment as well as <code>gap</code>.</p>
  607. <p>The <code>gap</code> property slips in another excellent feature from the container queries spec which is container units. The <code>cqi</code> unit we’ve used stands for “container query inline”, so effectively this value will render as 5% of the calculated inline size, expanding for larger spaces and shrinking for smaller spaces.</p>
  608. <p>One more adjustment for this variation is to stack the number and icon, so we'll add those styles to the container query.</p>
  609. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size &gt; 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  610. <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span>
  611. <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
  612. <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span>
  613. <span class="token punctuation">}</span>
  614. <span class="token punctuation">}</span></code></pre>
  615. <p>There’s one last adjustment we have, and it will be based on how much room the card grid layout has available. That means we’ll switch and query the <code>layout-container</code>.</p>
  616. <p>The adjustment is to set an <code>aspect-ratio</code> for the default card variations. We’ll also have to add a style to <code>unset</code> the ratio for the wide variation.</p>
  617. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> layout-container <span class="token punctuation">(</span>inline-size &gt; 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  618. <span class="token selector">.card</span> <span class="token punctuation">{</span>
  619. <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span>
  620. <span class="token punctuation">}</span>
  621. <span class="token punctuation">}</span>
  622. <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size &gt; 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  623. <span class="token selector">.card</span> <span class="token punctuation">{</span>
  624. <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  625. <span class="token punctuation">}</span>
  626. <span class="token punctuation">}</span></code></pre>
  627. <p>You may safely use <code>aspect-ratio</code> without worry of content overflow because the ratio is forgiving, and allows content size to take precedence. Unless dimension properties also limit the element size, the <code>aspect-ratio</code> will allow content to increase the element’s size.</p>
  628. <p>That said, we will also place one dimension property of <code>max-width: 100%</code> on the card so that it stays within the confines of the grid item. Flexbox by itself will not force the element to a particular size, so the <code>aspect-ratio</code> could cause it to grow outside the flex item boundary. Adding <code>max-inline-size</code> will keep the growth in check while allowing longer content to increase the height when needed.</p>
  629. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> layout-container <span class="token punctuation">(</span>inline-size &gt; 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  630. <span class="token selector">.card</span> <span class="token punctuation">{</span>
  631. <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span>
  632. <span class="token property">max-inline-size</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  633. <span class="token punctuation">}</span>
  634. <span class="token punctuation">}</span></code></pre>
  635. <details false>
  636. <summary>CSS for "Card Container Queries"</summary>
  637. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> layout-container <span class="token punctuation">(</span>inline-size &gt; 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  638. <span class="token selector">.card</span> <span class="token punctuation">{</span>
  639. <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span>
  640. <span class="token property">max-width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
  641. <span class="token punctuation">}</span>
  642. <span class="token punctuation">}</span>
  643. <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size &gt; 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  644. <span class="token selector">.card</span> <span class="token punctuation">{</span>
  645. <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
  646. <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
  647. <span class="token property">justify-content</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span>
  648. <span class="token property">gap</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span>
  649. <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  650. <span class="token punctuation">}</span>
  651. <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span>
  652. <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
  653. <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span>
  654. <span class="token punctuation">}</span>
  655. <span class="token punctuation">}</span>
  656. </code></pre>
  657. </details>
  658. <div class="heading-wrapper h3">
  659. <h3 id="container-query-fluid-type">Container Query Fluid Type</h3>
  660. <a class="anchor" href="#container-query-fluid-type" aria-labelledby="container-query-fluid-type"><span hidden>#</span></a></div>
  661. <p>According to our mockup, the last adjustment we need is to increase the font size as the card becomes wider.</p>
  662. <p>We’ll set up a range of allowed values using <code>clamp()</code>. This function accepts three values: a minimum, an ideal, and a maximum. If we provide a dynamic value for the middle ideal, then the browser can interpolate between the minimum and maximum.</p>
  663. <p>We’ll use the <code>cqi</code> unit for the ideal value, which means the <code>font-size</code> will be relative to the inline size of the card. Therefore, narrower cards will render a <code>font-size</code> toward the minimum end of the range, and wider cards will have a <code>font-size</code> toward the maximum end.</p>
  664. <p>A neat thing about container queries is that all elements are style containers by default. This means there is no need to wrap a rule with a container query to use container query units - they are available to all elements!</p>
  665. <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span>
  666. <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.25rem<span class="token punctuation">,</span> 5cqi<span class="token punctuation">,</span> 1.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
  667. <span class="token punctuation">}</span></code></pre>
  668. <blockquote>
  669. <p>While this technique is more than sufficient for a single component, you may be interested in my article covering <a href="https://moderncss.dev/container-query-units-and-fluid-typography/">three fluid typography techniques</a> applied via a “mixin” using custom properties.</p>
  670. </blockquote>
  671. <p>One last modern CSS feature we'll use to conclude our card styles is an experimental Chrome-only feature. Use of <code>text-wrap: balance</code> will evaluate a text block of up to four lines and "balance" it by inserting visual line breaks. This helps short passages of text, like headlines, have a more pleasing appearance. It's a great progressive enhancement because it looks great if it works and doesn't cause harm if it fails. However, balancing does not change an element's computed width, so a side-effect in some layouts may be an increase in unwanted space next to the text.</p>
  672. <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span>
  673. <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span>
  674. <span class="token punctuation">}</span></code></pre>
  675. <details false>
  676. <summary>CSS for "Card Fluid Type"</summary>
  677. <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span>
  678. <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.25rem<span class="token punctuation">,</span> 5cqi<span class="token punctuation">,</span> 1.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
  679. <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span>
  680. <span class="token punctuation">}</span>
  681. </code></pre>
  682. </details>
  683. <p>The pagination component benefits from container size queries since it is expected to modify the visibility of elements depending on the available inline space.</p>
  684. <p><img src="https://moderncss.dev/img/posts/32/preview-pagination.png" alt=""></p>
  685. <p>The default view which appears at the narrowest space will show only the <code>.pagination-label</code> and the arrow icons from the “Previous” and “Next” controls.</p>
  686. <p>In slightly wider spaces, the labels for the “Previous” and “Next” controls will be visible.</p>
  687. <p>Finally, once there is enough inline space, the <code>.pagination-label</code> will be swapped out for the full <code>.pagination-list</code> with numbered links to each page.</p>
  688. <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pagination-container<span class="token punctuation">"</span></span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Pagination<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  689. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pagination-nav pagination-nav__prev<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  690. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token punctuation">/&gt;</span></span>
  691. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pagination-nav__label<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Previous<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
  692. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
  693. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pagination-label<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Page 3 of 8<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
  694. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pagination-list<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  695. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span>
  696. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>
  697. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pagination-nav pagination-nav__next<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  698. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token punctuation">/&gt;</span></span>
  699. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pagination-nav__label<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Next<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
  700. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
  701. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">&gt;</span></span></code></pre>
  702. <p>We'll first define containment for the <code>.pagination-container</code> to enable this dynamic layout behavior.</p>
  703. <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container</span> <span class="token punctuation">{</span>
  704. <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span>
  705. <span class="token punctuation">}</span></code></pre>
  706. <p>The styles for our default view have already hidden the <code>.pagination-list</code> and <code>.pagination-nav</code> labels. Important to note is that technique for hiding the <code>.pagination-nav</code> labels still makes the text available for users of assistive technology such as screen readers.</p>
  707. <p>Time for the first level of our container size queries, which is simply unsetting the styles currently hiding the <code>.pagination-nav</code> labels.</p>
  708. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 25ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  709. <span class="token selector">.pagination-nav__label</span> <span class="token punctuation">{</span>
  710. <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span>
  711. <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  712. <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  713. <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  714. <span class="token punctuation">}</span>
  715. <span class="token punctuation">}</span></code></pre>
  716. <p>Following that, we’ll add a container size query to hide the <code>.pagination-label</code> and reveal the full <code>.pagination-list</code>.</p>
  717. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 40ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  718. <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span>
  719. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  720. <span class="token punctuation">}</span>
  721. <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span>
  722. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  723. <span class="token punctuation">}</span>
  724. <span class="token punctuation">}</span></code></pre>
  725. <details false>
  726. <summary>CSS for "Pagination Container Queries"</summary>
  727. <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container</span> <span class="token punctuation">{</span>
  728. <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span>
  729. <span class="token punctuation">}</span>
  730. <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 25ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  731. <span class="token selector">.pagination-nav__label</span> <span class="token punctuation">{</span>
  732. <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span>
  733. <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  734. <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  735. <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  736. <span class="token punctuation">}</span>
  737. <span class="token punctuation">}</span>
  738. <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 40ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  739. <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span>
  740. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  741. <span class="token punctuation">}</span>
  742. <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span>
  743. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  744. <span class="token punctuation">}</span>
  745. <span class="token punctuation">}</span>
  746. </code></pre>
  747. </details>
  748. <div class="heading-wrapper h3">
  749. <h3 id="using-has-for-quantity-queries">Using <code>:has()</code> for Quantity Queries</h3>
  750. <a class="anchor" href="#using-has-for-quantity-queries" aria-labelledby="using-has-for-quantity-queries"><span hidden>#</span></a></div>
  751. <p>While the pagination layout transition happens smoothly for the current list of items, we have a potential problem. Eventually, the pagination list could grow much larger than ten items, which may lead to overflow if the container isn’t actually wide enough to hold the larger list.</p>
  752. <p>To help manage that condition, we can bring back <code>:has()</code> and use it to create quantity queries, which means modifying styles based on checking the number of items.</p>
  753. <p>We'd like to keep the medium appearance for the pagination component if the list has more than 10 items. To check for that quantity, we can use <code>:has()</code> with <code>:nth-child</code> and check for an 11th item. This signifies that list has at least 11 items, which exceeds the list limit of 10.</p>
  754. <p>We must place this rule within the "large" container query so that it overrides the other styles we planned for lists with 10 or fewer items and doesn't apply too early.</p>
  755. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 40ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  756. <span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span>
  757. <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span>
  758. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  759. <span class="token punctuation">}</span>
  760. <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span>
  761. <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
  762. <span class="token punctuation">}</span>
  763. <span class="token punctuation">}</span>
  764. <span class="token punctuation">}</span></code></pre>
  765. <details false>
  766. <summary>CSS for "Pagination Quantity Queries"</summary>
  767. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 40ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  768. <span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span>
  769. <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span>
  770. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  771. <span class="token punctuation">}</span>
  772. <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span>
  773. <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
  774. <span class="token punctuation">}</span>
  775. <span class="token punctuation">}</span>
  776. <span class="token punctuation">}</span>
  777. </code></pre>
  778. </details>
  779. <p>You can open your browser dev tools and delete a couple of the list items to see the layout change to reveal the full list again once there are 10 or fewer.</p>
  780. <div class="heading-wrapper h3">
  781. <h3 id="upgrading-to-style-queries">Upgrading to Style Queries</h3>
  782. <a class="anchor" href="#upgrading-to-style-queries" aria-labelledby="upgrading-to-style-queries"><span hidden>#</span></a></div>
  783. <p>So far, we’ve been working with container size queries, but another type is container style queries. This means the ability to query against the computed values of CSS properties of a container.</p>
  784. <p>Just like size queries, style queries cannot style the container itself, just it’s children. But the property you are querying for must exist on the container.</p>
  785. <p>Use of a style query requires the <code>style</code> signifier prior to the query condition. Presently, support for style queries is available in Chromium within the scope of querying for custom property values.</p>
  786. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--my-property</span><span class="token punctuation">:</span> true<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  787. <span class="token punctuation">}</span></code></pre>
  788. <p>Instead of creating the quantity queries for the pagination component within the size query, we’ll switch and define a custom property for the <code>.pagination-container</code> to be used for a style query. This can be part of the default, non-container query rules for this element.</p>
  789. <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span>
  790. <span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span>
  791. <span class="token punctuation">}</span></code></pre>
  792. <p>A feature of custom properties is they can be almost any value, so here we’re using it to create a boolean toggle. I’ve picked the name <code>--show-label</code> because when this is true, we will show the <code>.pagination-label</code> instead of the <code>.pagination-list</code>.</p>
  793. <p>Now, while we can’t directly combine size and style container queries, we can nest the style query within the size query. This is important because just as before we also want to ensure these styles only apply for the larger container size query.</p>
  794. <p>The pagination-related styles remain the same; we've just switched the application to use a style query. The style query requires a value for the custom property, so we've borrowed the familiar convention of a boolean value to treat this like a toggle.</p>
  795. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 40ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  796. <span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  797. <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span>
  798. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  799. <span class="token punctuation">}</span>
  800. <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span>
  801. <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
  802. <span class="token punctuation">}</span>
  803. <span class="token punctuation">}</span>
  804. <span class="token punctuation">}</span></code></pre>
  805. <details false>
  806. <summary>CSS for "Pagination Style Queries"</summary>
  807. <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span>
  808. <span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span>
  809. <span class="token punctuation">}</span>
  810. <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 40ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  811. <span class="token atrule"><span class="token rule">@container</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  812. <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span>
  813. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  814. <span class="token punctuation">}</span>
  815. <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span>
  816. <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
  817. <span class="token punctuation">}</span>
  818. <span class="token punctuation">}</span>
  819. <span class="token punctuation">}</span>
  820. </code></pre>
  821. </details>
  822. <p>This navigation component is intended to contain a site's primary navigation links and branding. It features a fairly commonplace display of the logo followed by the top-level page links and then supplementary actions for "Login" and "Sign Up" placed on the opposite side.</p>
  823. <p>Once again, this component will benefit from container size and style queries to manage the visibility of elements depending on the amount of available inline space.</p>
  824. <p><img src="https://moderncss.dev/img/posts/32/preview-navigation.png" alt=""></p>
  825. <p>As the space narrows, the horizontal link list is replaced with a button labeled “Menu” which can toggle a dropdown version of the links. At even more narrow spaces, the logo collapses to hide the brand name text and leave only the logomark visible.</p>
  826. <p>To accomplish these views, we’ll leverage named containers to better target the container queries. The navigation wrapper will be named <code>navigation</code> and the area containing the links will be named <code>menu</code>. This allows us to treat the areas independently and contextually manage the behavior.</p>
  827. <p><img src="https://moderncss.dev/img/posts/32/spec-navigation.png" alt=""></p>
  828. <p>Here's our markup outline to help understand the relationships between our elements.</p>
  829. <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>navigation<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  830. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>navigation__brand<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Logo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
  831. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>navigation__menu<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  832. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span> <span class="token attr-name">aria-expanded</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>false<span class="token punctuation">"</span></span> <span class="token attr-name">aria-controls</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#menu<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  833. Menu
  834. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  835. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu<span class="token punctuation">"</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>list<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  836. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span>
  837. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  838. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>navigation__actions<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  839. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  840. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">&gt;</span></span></code></pre>
  841. <blockquote>
  842. <p>You'll likely find that building with container queries in mind may prompt rethinking your HTML structure and simplifying the hierarchy.</p>
  843. </blockquote>
  844. <p>An important part of our construction that’s already in place for the baseline styles is that the <code>.navigation</code> wrapper is setup to use CSS grid. In order for the <code>.navigation__menu</code> area to have an independent and variable container size to query for, we’ve use a grid column width of <code>1fr</code>. This means it is allowed to use all the remaining space leftover after the logo and actions elements reserve their share, which is accomplished by setting their column size to <code>auto</code>.</p>
  845. <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span>
  846. <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
  847. <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> auto 1fr auto<span class="token punctuation">;</span>
  848. <span class="token punctuation">}</span></code></pre>
  849. <p>The rest of our initial state is already in place, and presently assumes the most narrow context. The visible elements are the logomark, “Menu” button, and the additional actions. Now, we’ll use container queries to work out the visibility of the medium and large stages.</p>
  850. <p>The first step is defining the containers. We’ll use the <code>container</code> shorthand property, which accepts the container name first and then the container type, with a forward slash (<code>/</code>) as a separator.</p>
  851. <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span>
  852. <span class="token property">container</span><span class="token punctuation">:</span> navigation / inline-size<span class="token punctuation">;</span>
  853. <span class="token punctuation">}</span>
  854. <span class="token selector">.navigation__menu</span> <span class="token punctuation">{</span>
  855. <span class="token property">container</span><span class="token punctuation">:</span> menu / inline-size<span class="token punctuation">;</span>
  856. <span class="token punctuation">}</span></code></pre>
  857. <p>First, we'll query against the <code>navigation</code> container and allow the brand name to be visible once space allows. This component uses the same accessibly hidden technique as was used for the pagination, so the visibility styles may look familiar. Also, note the use of the media range syntax to apply the styles when the inline-size is greater than or equal to the comparison value.</p>
  858. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> navigation <span class="token punctuation">(</span>inline-size &gt;= 45ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  859. <span class="token selector">.navigation__brand span</span> <span class="token punctuation">{</span>
  860. <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span>
  861. <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  862. <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  863. <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  864. <span class="token punctuation">}</span>
  865. <span class="token punctuation">}</span></code></pre>
  866. <p>The second stage is to reveal the link list and hide the “Menu” button. This will be based on the amount of space the <code>menu</code> container area has, thanks to the grid flexibility noted earlier.</p>
  867. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> menu <span class="token punctuation">(</span>inline-size &gt;= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  868. <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span>
  869. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  870. <span class="token punctuation">}</span>
  871. <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span>
  872. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  873. <span class="token punctuation">}</span>
  874. <span class="token punctuation">}</span></code></pre>
  875. <details false>
  876. <summary>CSS for "Navigation Container Queries"</summary>
  877. <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span>
  878. <span class="token property">container</span><span class="token punctuation">:</span> navigation / inline-size<span class="token punctuation">;</span>
  879. <span class="token punctuation">}</span>
  880. <span class="token selector">.navigation__menu</span> <span class="token punctuation">{</span>
  881. <span class="token property">container</span><span class="token punctuation">:</span> menu / inline-size<span class="token punctuation">;</span>
  882. <span class="token punctuation">}</span>
  883. <span class="token atrule"><span class="token rule">@container</span> navigation <span class="token punctuation">(</span>inline-size &gt;= 45ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  884. <span class="token selector">.navigation__brand span</span> <span class="token punctuation">{</span>
  885. <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span>
  886. <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  887. <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span>
  888. <span class="token punctuation">}</span>
  889. <span class="token punctuation">}</span>
  890. <span class="token atrule"><span class="token rule">@container</span> menu <span class="token punctuation">(</span>inline-size &gt;= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  891. <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span>
  892. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  893. <span class="token punctuation">}</span>
  894. <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span>
  895. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  896. <span class="token punctuation">}</span>
  897. <span class="token punctuation">}</span>
  898. </code></pre>
  899. </details>
  900. <blockquote>
  901. <p>Given the demo size constraints, you may not see the list until you resize the demo container larger.</p>
  902. </blockquote>
  903. <div class="heading-wrapper h3">
  904. <h3 id="improve-scalability-with-quantity-and-style-queries">Improve Scalability With Quantity and Style Queries</h3>
  905. <a class="anchor" href="#improve-scalability-with-quantity-and-style-queries" aria-labelledby="improve-scalability-with-quantity-and-style-queries"><span hidden>#</span></a></div>
  906. <p>Depending on the length of the link list, we may be able to reveal it a bit sooner. While we would still need JavaScript to compute the total dimension of the list, we can use a quantity query to anticipate the space to provide.</p>
  907. <p>Our present container size query for the <code>menu</code> container requires <code>80ch</code> of space. We will add a quantity query to create a condition of whether or not to show the links given a list with six or more items. We'll set the <code>--show-menu</code> property to true if that is met.</p>
  908. <pre class="language-css"><code class="language-css"><span class="token selector">.navigation__menu:has(:nth-child(6))</span> <span class="token punctuation">{</span>
  909. <span class="token property">--show-menu</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span>
  910. <span class="token punctuation">}</span></code></pre>
  911. <p>Now we'll add one more container size query with a nested style query. The size query will take advantage of the media range syntax again, this time to create a comparison range. We'll provide both a lower and upper boundary and check if the <code>inline-size</code> is equal to or between those bounds, thanks to this new ability to use math operators for the query.</p>
  912. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> menu <span class="token punctuation">(</span>40ch &lt;= inline-size &lt;= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  913. <span class="token punctuation">}</span></code></pre>
  914. <p>Then, within that we nest a style query. The style rules are intended to keep the “Menu” button hidden and the link list visible, so we’ll also include the <code>not</code> operator. That means the rules should apply when the container does <em>not</em> meet the style query condition.</p>
  915. <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@container</span> menu <span class="token punctuation">(</span>40ch &lt;= inline-size &lt;= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  916. <span class="token atrule"><span class="token rule">@container</span> <span class="token keyword">not</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--show-menu</span><span class="token punctuation">:</span> true<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  917. <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span>
  918. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  919. <span class="token punctuation">}</span>
  920. <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span>
  921. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  922. <span class="token punctuation">}</span>
  923. <span class="token punctuation">}</span>
  924. <span class="token punctuation">}</span></code></pre>
  925. <p>Important to note is that the container size query we already wrote for the <code>menu</code> container when it is sized <code>&gt;= 60ch</code> should remain as is, otherwise the display will flip back to prioritizing the “Menu” button above <code>60ch</code>.</p>
  926. <details false>
  927. <summary>CSS for "Navigation Quantity &amp; Style Queries"</summary>
  928. <pre class="language-css"><code class="language-css"><span class="token selector">.navigation__menu:has(:nth-child(6))</span> <span class="token punctuation">{</span>
  929. <span class="token property">--show-menu</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span>
  930. <span class="token punctuation">}</span>
  931. <span class="token atrule"><span class="token rule">@container</span> menu <span class="token punctuation">(</span>40ch &lt;= inline-size &lt;= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  932. <span class="token atrule"><span class="token rule">@container</span> <span class="token keyword">not</span> <span class="token function">style</span><span class="token punctuation">(</span><span class="token property">--show-menu</span><span class="token punctuation">:</span> true<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  933. <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span>
  934. <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
  935. <span class="token punctuation">}</span>
  936. <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span>
  937. <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
  938. <span class="token punctuation">}</span>
  939. <span class="token punctuation">}</span>
  940. <span class="token punctuation">}</span>
  941. </code></pre>
  942. </details>
  943. <div class="heading-wrapper h3">
  944. <h3 id="container-queries-accessibility-and-fail-safe-resizing">Container Queries, Accessibility, and Fail-Safe Resizing</h3>
  945. <a class="anchor" href="#container-queries-accessibility-and-fail-safe-resizing" aria-labelledby="container-queries-accessibility-and-fail-safe-resizing"><span hidden>#</span></a></div>
  946. <p>Since container queries enable independent layout adjustments of component parts, they can help to meet the <a href="https://www.w3.org/WAI/WCAG22/Understanding/reflow.html">WCAG criterion for reflow</a>. The term "reflow" refers to supporting desktop zoom of up to 400% given a minimum resolution of 1280px, which at 400% computes to <code>320px</code> of inline space.</p>
  947. <blockquote>
  948. <p>Discussing reflow is not new here on ModernCSS - learn more about reflow and other <a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">modern CSS upgrades to improve accessibility</a>.</p>
  949. </blockquote>
  950. <p>While we don’t have a “zoom” media query, both media queries and container queries that affect the layout approaching <code>320px</code> will have an impact. The goal of the reflow criterion is to prevent horizontal scroll by “reflowing” content into a single column.</p>
  951. <p>Taking our navigation as an example, here's a video demonstration of increasing zoom to 400%. Notice how the layout changes similarly to narrowing the viewport.</p>
  952. <video controls>
  953. <source src="https://moderncss.dev/img/posts/32/navigation-a11y.mp4" type="video/mp4">
  954. </source></video>
  955. <blockquote>
  956. <p>The advantage of container queries is that they are more likely to succeed under zoom conditions than media queries which may be tied to a presumed set of "breakpoints."</p>
  957. </blockquote>
  958. <p>Often, the set of breakpoints frameworks use can begin to fail at the in-between conditions that aren't precisely a match for device dimensions. Those may be hit by zoom or other conditions like split-screen usage.</p>
  959. <p>Thoughtful usage of container queries makes your components and layouts far more resilient across unknown conditions, whether those conditions are related to device size, user capabilities, or contexts only an AI bot could dream up.</p>
  960. <div class="heading-wrapper h2">
  961. <h2 id="supporting-and-using-modern-css-features">Supporting and Using Modern CSS Features</h2>
  962. <a class="anchor" href="#supporting-and-using-modern-css-features" aria-labelledby="supporting-and-using-modern-css-features"><span hidden>#</span></a></div>
  963. <p>The previous post in this series is all about <a href="https://moderncss.dev/testing-feature-support-for-modern-css/">testing features support for modern CSS features</a>. However, there’s one consideration that is top of mind for me when choosing what features to begin using.</p>
  964. <p>When evaluating whether a feature is "safe to use" with your users, considering the impact of the feature you're looking to integrate weighs heavily in the decision. For example, some modern CSS features are "nice to haves" that provide an updated experience that's great when they work but also don't necessarily cause an interruption in the user experience should they fail.</p>
  965. <p>The features we reviewed today can absolutely have a large impact, but the context of how they are used also matters. The ways we incorporated modern CSS in the components were, by and large, progressive enhancements, meaning they would fail gracefully and have minimal impact.</p>
  966. <p>It's always important to consider the real users accessing your applications or content. Therefore, you may decide to prepare fallbacks, such as a set of styles that uses viewport units when container queries are unavailable. Or, switching some of the <code>:has()</code> logic to require a few extra classes for applying the styles until you are more comfortable with the level of support.</p>
  967. <blockquote>
  968. <p>As a quick measure, <strong>consider whether a user would be prevented from doing the tasks they need to do on your website</strong> if the modern feature fails.</p>
  969. </blockquote>
  970. <p>Remember: there's no need to use everything new right away, but learning about what's available is beneficial so you can confidently craft a resilient solution.</p>
  971. <hr>
  972. <p>This material was originally presented at CSS Day 2023, and you may <a href="https://noti.st/st3ph/ea40FC/modern-css-for-dynamic-component-based-architecture">review the slides</a>.</p>
  973. </article>
  974. <hr>
  975. <footer>
  976. <p>
  977. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  978. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  979. </svg> Accueil</a> •
  980. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  981. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  982. </svg> Suivre</a> •
  983. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  984. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  985. </svg> Pro</a> •
  986. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  987. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  988. </svg> Email</a> •
  989. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  990. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  991. </svg> Légal</abbr>
  992. </p>
  993. <template id="theme-selector">
  994. <form>
  995. <fieldset>
  996. <legend><svg class="icon icon-brightness-contrast">
  997. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  998. </svg> Thème</legend>
  999. <label>
  1000. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  1001. </label>
  1002. <label>
  1003. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  1004. </label>
  1005. <label>
  1006. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  1007. </label>
  1008. </fieldset>
  1009. </form>
  1010. </template>
  1011. </footer>
  1012. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  1013. <script>
  1014. function loadThemeForm(templateName) {
  1015. const themeSelectorTemplate = document.querySelector(templateName)
  1016. const form = themeSelectorTemplate.content.firstElementChild
  1017. themeSelectorTemplate.replaceWith(form)
  1018. form.addEventListener('change', (e) => {
  1019. const chosenColorScheme = e.target.value
  1020. localStorage.setItem('theme', chosenColorScheme)
  1021. toggleTheme(chosenColorScheme)
  1022. })
  1023. const selectedTheme = localStorage.getItem('theme')
  1024. if (selectedTheme && selectedTheme !== 'undefined') {
  1025. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  1026. }
  1027. }
  1028. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  1029. window.addEventListener('load', () => {
  1030. let hasDarkRules = false
  1031. for (const styleSheet of Array.from(document.styleSheets)) {
  1032. let mediaRules = []
  1033. for (const cssRule of styleSheet.cssRules) {
  1034. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  1035. continue
  1036. }
  1037. // WARNING: Safari does not have/supports `conditionText`.
  1038. if (cssRule.conditionText) {
  1039. if (cssRule.conditionText !== prefersColorSchemeDark) {
  1040. continue
  1041. }
  1042. } else {
  1043. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  1044. continue
  1045. }
  1046. }
  1047. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  1048. }
  1049. // WARNING: do not try to insert a Rule to a styleSheet you are
  1050. // currently iterating on, otherwise the browser will be stuck
  1051. // in a infinite loop…
  1052. for (const mediaRule of mediaRules) {
  1053. styleSheet.insertRule(mediaRule.cssText)
  1054. hasDarkRules = true
  1055. }
  1056. }
  1057. if (hasDarkRules) {
  1058. loadThemeForm('#theme-selector')
  1059. }
  1060. })
  1061. </script>
  1062. </body>
  1063. </html>